diff --git a/src/App.tsx b/src/App.tsx index 18daf2e..2de3aef 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,12 @@ import { Toaster as Sonner } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { BrowserRouter, Routes, Route } from "react-router-dom"; -import Index from "./pages/Index"; +import Dashboard from "./pages/Dashboard"; +import Partners from "./pages/Partners"; +import Events from "./pages/Events"; +import Users from "./pages/Users"; +import Financials from "./pages/Financials"; +import Settings from "./pages/Settings"; import NotFound from "./pages/NotFound"; const queryClient = new QueryClient(); @@ -15,7 +20,12 @@ const App = () => ( - } /> + } /> + } /> + } /> + } /> + } /> + } /> {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} } /> diff --git a/src/components/dashboard/ActionItemsPanel.tsx b/src/components/dashboard/ActionItemsPanel.tsx new file mode 100644 index 0000000..c74220f --- /dev/null +++ b/src/components/dashboard/ActionItemsPanel.tsx @@ -0,0 +1,86 @@ +import { Link } from 'react-router-dom'; +import { + UserCheck, + Flag, + Wallet, + AlertTriangle, + ArrowRight +} from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { ActionItem } from '@/types/dashboard'; +import { formatCurrency } from '@/data/mockData'; + +interface ActionItemsPanelProps { + items: ActionItem[]; +} + +const iconMap = { + kyc: UserCheck, + flagged: Flag, + payout: Wallet, + stripe: AlertTriangle, +}; + +const colorMap = { + kyc: 'text-warning bg-warning/10', + flagged: 'text-error bg-error/10', + payout: 'text-success bg-success/10', + stripe: 'text-warning bg-warning/10', +}; + +export function ActionItemsPanel({ items }: ActionItemsPanelProps) { + return ( +
+
+
+

Pending Actions

+

Items requiring your attention

+
+ + {items.length} urgent + +
+ +
+ {items.map((item) => { + const Icon = iconMap[item.type]; + const colorClass = colorMap[item.type]; + + return ( + +
+ +
+ +
+
+ + {item.type === 'payout' ? formatCurrency(item.count) : item.count} + +

{item.title}

+
+

{item.description}

+
+ + + + ); + })} +
+
+ ); +} diff --git a/src/components/dashboard/ActivityFeed.tsx b/src/components/dashboard/ActivityFeed.tsx new file mode 100644 index 0000000..b11d10a --- /dev/null +++ b/src/components/dashboard/ActivityFeed.tsx @@ -0,0 +1,85 @@ +import { + Users, + Calendar, + Wallet, + User, + CheckCircle2, + Clock, + AlertTriangle, + XCircle +} from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { ActivityItem } from '@/types/dashboard'; +import { formatRelativeTime } from '@/data/mockData'; + +interface ActivityFeedProps { + items: ActivityItem[]; +} + +const typeIconMap = { + partner: Users, + event: Calendar, + payout: Wallet, + user: User, +}; + +const statusIconMap = { + success: CheckCircle2, + pending: Clock, + warning: AlertTriangle, + error: XCircle, +}; + +const statusColorMap = { + success: 'text-success', + pending: 'text-warning', + warning: 'text-warning', + error: 'text-error', +}; + +export function ActivityFeed({ items }: ActivityFeedProps) { + return ( +
+
+
+

Recent Activity

+

Latest platform updates

+
+
+ +
+ {items.map((item, index) => { + const TypeIcon = typeIconMap[item.type]; + const StatusIcon = statusIconMap[item.status]; + const statusColor = statusColorMap[item.status]; + + return ( +
+
+ +
+ +
+
+

{item.title}

+ +
+

{item.description}

+
+ + + {formatRelativeTime(item.timestamp)} + +
+ ); + })} +
+
+ ); +} diff --git a/src/components/dashboard/DashboardMetricCard.tsx b/src/components/dashboard/DashboardMetricCard.tsx new file mode 100644 index 0000000..b6161ad --- /dev/null +++ b/src/components/dashboard/DashboardMetricCard.tsx @@ -0,0 +1,64 @@ +import { LucideIcon, TrendingUp, TrendingDown } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +interface DashboardMetricCardProps { + title: string; + value: string; + subtitle?: string; + icon: LucideIcon; + trend?: { + value: number; + label: string; + positive?: boolean; + }; + iconColor?: string; +} + +export function DashboardMetricCard({ + title, + value, + subtitle, + icon: Icon, + trend, + iconColor = "text-accent" +}: DashboardMetricCardProps) { + return ( +
+
+
+

{title}

+

{value}

+ + {trend && ( +
+ {trend.positive !== false ? ( + + ) : ( + + )} + + {trend.positive !== false ? '+' : ''}{trend.value}% + + {trend.label} +
+ )} + + {subtitle && !trend && ( +

{subtitle}

+ )} +
+ +
+ +
+
+
+ ); +} diff --git a/src/components/dashboard/RevenueChart.tsx b/src/components/dashboard/RevenueChart.tsx new file mode 100644 index 0000000..412c303 --- /dev/null +++ b/src/components/dashboard/RevenueChart.tsx @@ -0,0 +1,76 @@ +import { RevenueDataPoint } from '@/types/dashboard'; +import { formatCurrency } from '@/data/mockData'; + +interface RevenueChartProps { + data: RevenueDataPoint[]; +} + +export function RevenueChart({ data }: RevenueChartProps) { + const maxValue = Math.max(...data.map(d => Math.max(d.revenue, d.payouts))); + + return ( +
+
+
+

Revenue vs Payouts

+

Last 7 days comparison

+
+
+
+ + Revenue +
+
+ + Payouts +
+
+
+ + {/* Chart Area */} +
+ {data.map((point, index) => ( +
+
+ {/* Revenue Bar */} +
+ {/* Payout Bar */} +
+
+ {point.day} +
+ ))} +
+ + {/* Summary */} +
+
+

+ {formatCurrency(data.reduce((sum, d) => sum + d.revenue, 0))} +

+

Total Revenue

+
+
+

+ {formatCurrency(data.reduce((sum, d) => sum + d.payouts, 0))} +

+

Total Payouts

+
+
+
+ ); +} diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx new file mode 100644 index 0000000..849d070 --- /dev/null +++ b/src/components/layout/AppLayout.tsx @@ -0,0 +1,23 @@ +import { ReactNode } from 'react'; +import { Sidebar } from './Sidebar'; +import { TopBar } from './TopBar'; + +interface AppLayoutProps { + children: ReactNode; + title: string; + description?: string; +} + +export function AppLayout({ children, title, description }: AppLayoutProps) { + return ( +
+ +
+ +
+ {children} +
+
+
+ ); +} diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx new file mode 100644 index 0000000..af7a8f7 --- /dev/null +++ b/src/components/layout/Sidebar.tsx @@ -0,0 +1,82 @@ +import { NavLink, useLocation } from 'react-router-dom'; +import { + LayoutDashboard, + Users, + Calendar, + User, + DollarSign, + Settings, + Ticket +} from 'lucide-react'; +import { cn } from '@/lib/utils'; + +const navItems = [ + { title: 'Dashboard', href: '/', icon: LayoutDashboard }, + { title: 'Partner Management', href: '/partners', icon: Users }, + { title: 'Events', href: '/events', icon: Calendar }, + { title: 'Users', href: '/users', icon: User }, + { title: 'Financials', href: '/financials', icon: DollarSign }, + { title: 'Settings', href: '/settings', icon: Settings }, +]; + +export function Sidebar() { + const location = useLocation(); + + return ( + + ); +} diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx new file mode 100644 index 0000000..cbe50af --- /dev/null +++ b/src/components/layout/TopBar.tsx @@ -0,0 +1,64 @@ +import { Search, Bell, ChevronDown } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +interface TopBarProps { + title: string; + description?: string; +} + +export function TopBar({ title, description }: TopBarProps) { + return ( +
+ {/* Page Title */} +
+

{title}

+ {description && ( +

{description}

+ )} +
+ + {/* Right Section */} +
+ {/* Search */} +
+ + +
+ + {/* Notifications */} + + + {/* Profile */} + +
+
+ ); +} diff --git a/src/data/mockData.ts b/src/data/mockData.ts new file mode 100644 index 0000000..8909303 --- /dev/null +++ b/src/data/mockData.ts @@ -0,0 +1,216 @@ +import { + DashboardMetrics, + ActionItem, + ActivityItem, + RevenueDataPoint, + Partner, + Event +} from '@/types/dashboard'; + +export const mockDashboardMetrics: DashboardMetrics = { + totalRevenue: 2450000, + revenueGrowth: 12.5, + activePartners: 156, + pendingPartners: 12, + liveEvents: 43, + eventsToday: 8, + ticketSales: 12847, + ticketGrowth: 8.3, +}; + +export const mockActionItems: ActionItem[] = [ + { + id: '1', + type: 'kyc', + count: 5, + title: 'Partner Approval Queue', + description: 'Partners awaiting KYC verification', + href: '/partners?filter=pending', + priority: 'high', + }, + { + id: '2', + type: 'flagged', + count: 3, + title: 'Flagged Events', + description: 'Events reported for review', + href: '/events?filter=flagged', + priority: 'high', + }, + { + id: '3', + type: 'payout', + count: 845000, + title: 'Pending Payouts', + description: 'Ready for release', + href: '/financials?tab=payouts', + priority: 'medium', + }, + { + id: '4', + type: 'stripe', + count: 2, + title: 'Stripe Issues', + description: 'Connected accounts need attention', + href: '/partners?filter=stripe-issues', + priority: 'medium', + }, +]; + +export const mockActivityItems: ActivityItem[] = [ + { + id: '1', + type: 'partner', + title: 'New Partner Registered', + description: 'EventPro Solutions submitted KYC documents', + timestamp: new Date(Date.now() - 1000 * 60 * 15), + status: 'pending', + }, + { + id: '2', + type: 'event', + title: 'Event Approved', + description: 'Mumbai Music Festival is now live', + timestamp: new Date(Date.now() - 1000 * 60 * 45), + status: 'success', + }, + { + id: '3', + type: 'payout', + title: 'Payout Completed', + description: '₹2,45,000 transferred to TechConf India', + timestamp: new Date(Date.now() - 1000 * 60 * 120), + status: 'success', + }, + { + id: '4', + type: 'event', + title: 'Event Flagged', + description: 'Reported content in "Night Club Party"', + timestamp: new Date(Date.now() - 1000 * 60 * 180), + status: 'warning', + }, + { + id: '5', + type: 'user', + title: 'Admin Login', + description: 'Priya Sharma logged in from Mumbai', + timestamp: new Date(Date.now() - 1000 * 60 * 240), + status: 'success', + }, +]; + +export const mockRevenueData: RevenueDataPoint[] = [ + { day: 'Mon', revenue: 245000, payouts: 180000 }, + { day: 'Tue', revenue: 312000, payouts: 220000 }, + { day: 'Wed', revenue: 289000, payouts: 195000 }, + { day: 'Thu', revenue: 378000, payouts: 280000 }, + { day: 'Fri', revenue: 456000, payouts: 340000 }, + { day: 'Sat', revenue: 523000, payouts: 390000 }, + { day: 'Sun', revenue: 247000, payouts: 185000 }, +]; + +export const mockPartners: Partner[] = [ + { + id: '1', + name: 'EventPro Solutions', + email: 'contact@eventpro.in', + kycStatus: 'pending', + stripeStatus: 'pending', + totalRevenue: 0, + eventsCount: 0, + createdAt: new Date(Date.now() - 1000 * 60 * 60 * 2), + }, + { + id: '2', + name: 'TechConf India', + email: 'hello@techconf.in', + kycStatus: 'approved', + stripeStatus: 'connected', + totalRevenue: 1250000, + eventsCount: 15, + createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 90), + }, + { + id: '3', + name: 'Music Nights', + email: 'booking@musicnights.com', + kycStatus: 'approved', + stripeStatus: 'connected', + totalRevenue: 890000, + eventsCount: 28, + createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 45), + }, +]; + +export const mockEvents: Event[] = [ + { + id: '1', + title: 'Mumbai Music Festival', + partnerId: '3', + partnerName: 'Music Nights', + date: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), + status: 'live', + ticketsSold: 2450, + revenue: 489000, + }, + { + id: '2', + title: 'Tech Summit 2024', + partnerId: '2', + partnerName: 'TechConf India', + date: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14), + status: 'published', + ticketsSold: 890, + revenue: 445000, + }, + { + id: '3', + title: 'Night Club Party', + partnerId: '3', + partnerName: 'Music Nights', + date: new Date(Date.now() + 1000 * 60 * 60 * 24 * 3), + status: 'flagged', + ticketsSold: 120, + revenue: 24000, + }, +]; + +// Helper function to format currency in Indian format +export function formatCurrency(amount: number): string { + if (amount >= 10000000) { + return `₹${(amount / 10000000).toFixed(2)}Cr`; + } else if (amount >= 100000) { + return `₹${(amount / 100000).toFixed(2)}L`; + } else if (amount >= 1000) { + return `₹${(amount / 1000).toFixed(1)}K`; + } + return `₹${amount.toLocaleString('en-IN')}`; +} + +// Helper to format large numbers +export function formatNumber(num: number): string { + if (num >= 1000000) { + return `${(num / 1000000).toFixed(1)}M`; + } else if (num >= 1000) { + return `${(num / 1000).toFixed(1)}K`; + } + return num.toLocaleString('en-IN'); +} + +// Helper to format relative time +export function formatRelativeTime(date: Date): string { + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffMins = Math.floor(diffMs / (1000 * 60)); + const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + if (diffMins < 60) { + return `${diffMins}m ago`; + } else if (diffHours < 24) { + return `${diffHours}h ago`; + } else { + return `${diffDays}d ago`; + } +} diff --git a/src/index.css b/src/index.css index 4844bbd..0e1eec2 100644 --- a/src/index.css +++ b/src/index.css @@ -2,95 +2,104 @@ @tailwind components; @tailwind utilities; -/* Definition of the design system. All colors, gradients, fonts, etc should be defined here. -All colors MUST be HSL. +/* Eventify Backoffice - Neumorphic Blue Theme Design System + All colors MUST be HSL. */ @layer base { :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; + /* Neumorphic Blue Theme - Primary Palette */ + --neu-base: 216 33% 94%; /* #E8EFF8 - Main background */ + --neu-surface: 216 30% 92%; /* #DFE9F5 - Card surfaces */ + --neu-raised: 216 33% 96%; /* Lighter for raised elements */ + --neu-inset: 216 30% 88%; /* Darker for inset/pressed */ + + /* Brand Colors */ + --deep-blue: 220 60% 15%; /* #0F1E3D - Primary text */ + --royal-blue: 222 75% 33%; /* #1E3A8A - Active states */ + --ocean-blue: 217 91% 60%; /* #3B82F6 - Interactive */ + --sky-blue: 199 89% 48%; /* #0EA5E9 - Highlights */ + --ice-blue: 199 95% 74%; /* #7DD3FC - Subtle accents */ + + /* Semantic Colors */ + --success: 142 76% 36%; /* Green for positive */ + --success-foreground: 0 0% 100%; + --warning: 38 92% 50%; /* Amber for warnings */ + --warning-foreground: 0 0% 100%; + --error: 0 84% 60%; /* Red for errors */ + --error-foreground: 0 0% 100%; + + /* Base shadcn tokens mapped to neumorphic theme */ + --background: 216 33% 94%; + --foreground: 220 60% 15%; - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; + --card: 216 30% 92%; + --card-foreground: 220 60% 15%; - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; + --popover: 216 30% 92%; + --popover-foreground: 220 60% 15%; - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; + --primary: 222 75% 33%; + --primary-foreground: 0 0% 100%; - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; + --secondary: 216 30% 88%; + --secondary-foreground: 220 60% 15%; - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; + --muted: 216 30% 88%; + --muted-foreground: 220 30% 40%; - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; + --accent: 217 91% 60%; + --accent-foreground: 0 0% 100%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; + --destructive: 0 84% 60%; + --destructive-foreground: 0 0% 100%; - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; + --border: 216 25% 85%; + --input: 216 30% 88%; + --ring: 217 91% 60%; - --radius: 0.5rem; + --radius: 0.75rem; - --sidebar-background: 0 0% 98%; - - --sidebar-foreground: 240 5.3% 26.1%; - - --sidebar-primary: 240 5.9% 10%; - - --sidebar-primary-foreground: 0 0% 98%; - - --sidebar-accent: 240 4.8% 95.9%; - - --sidebar-accent-foreground: 240 5.9% 10%; - - --sidebar-border: 220 13% 91%; - - --sidebar-ring: 217.2 91.2% 59.8%; + /* Sidebar tokens */ + --sidebar-background: 216 30% 92%; + --sidebar-foreground: 220 60% 15%; + --sidebar-primary: 222 75% 33%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 216 30% 88%; + --sidebar-accent-foreground: 220 60% 15%; + --sidebar-border: 216 25% 85%; + --sidebar-ring: 217 91% 60%; } .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; + /* Dark mode not used in neumorphic theme - keeping light values */ + --background: 216 33% 94%; + --foreground: 220 60% 15%; + --card: 216 30% 92%; + --card-foreground: 220 60% 15%; + --popover: 216 30% 92%; + --popover-foreground: 220 60% 15%; + --primary: 222 75% 33%; + --primary-foreground: 0 0% 100%; + --secondary: 216 30% 88%; + --secondary-foreground: 220 60% 15%; + --muted: 216 30% 88%; + --muted-foreground: 220 30% 40%; + --accent: 217 91% 60%; + --accent-foreground: 0 0% 100%; + --destructive: 0 84% 60%; + --destructive-foreground: 0 0% 100%; + --border: 216 25% 85%; + --input: 216 30% 88%; + --ring: 217 91% 60%; + --sidebar-background: 216 30% 92%; + --sidebar-foreground: 220 60% 15%; + --sidebar-primary: 222 75% 33%; --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; + --sidebar-accent: 216 30% 88%; + --sidebar-accent-foreground: 220 60% 15%; + --sidebar-border: 216 25% 85%; + --sidebar-ring: 217 91% 60%; } } @@ -100,6 +109,55 @@ All colors MUST be HSL. } body { - @apply bg-background text-foreground; + @apply bg-background text-foreground antialiased; + } +} + +@layer components { + /* Neumorphic utility classes */ + .neu-card { + @apply bg-card rounded-2xl transition-all duration-200; + box-shadow: + 6px 6px 12px hsl(var(--neu-inset)), + -6px -6px 12px hsl(var(--neu-raised)); + } + + .neu-card-hover:hover { + box-shadow: + 8px 8px 16px hsl(var(--neu-inset)), + -8px -8px 16px hsl(var(--neu-raised)); + } + + .neu-inset { + @apply bg-secondary rounded-xl; + box-shadow: + inset 3px 3px 6px hsl(var(--neu-inset)), + inset -3px -3px 6px hsl(var(--neu-raised)); + } + + .neu-button { + @apply bg-card rounded-xl transition-all duration-200 cursor-pointer; + box-shadow: + 4px 4px 8px hsl(var(--neu-inset)), + -4px -4px 8px hsl(var(--neu-raised)); + } + + .neu-button:hover { + box-shadow: + 6px 6px 12px hsl(var(--neu-inset)), + -6px -6px 12px hsl(var(--neu-raised)); + } + + .neu-button:active { + box-shadow: + inset 2px 2px 4px hsl(var(--neu-inset)), + inset -2px -2px 4px hsl(var(--neu-raised)); + } + + .neu-button-active { + @apply bg-primary text-primary-foreground; + box-shadow: + inset 2px 2px 4px hsl(222 75% 28%), + inset -2px -2px 4px hsl(222 75% 38%); } } diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx new file mode 100644 index 0000000..1a8b3e8 --- /dev/null +++ b/src/pages/Dashboard.tsx @@ -0,0 +1,79 @@ +import { IndianRupee, Users, Calendar, Ticket } from 'lucide-react'; +import { AppLayout } from '@/components/layout/AppLayout'; +import { DashboardMetricCard } from '@/components/dashboard/DashboardMetricCard'; +import { ActionItemsPanel } from '@/components/dashboard/ActionItemsPanel'; +import { RevenueChart } from '@/components/dashboard/RevenueChart'; +import { ActivityFeed } from '@/components/dashboard/ActivityFeed'; +import { + mockDashboardMetrics, + mockActionItems, + mockRevenueData, + mockActivityItems, + formatCurrency, + formatNumber +} from '@/data/mockData'; + +export default function Dashboard() { + const metrics = mockDashboardMetrics; + + return ( + + {/* Metrics Grid */} +
+ + + + +
+ + {/* Main Content Grid */} +
+ {/* Left Column - Charts & Actions */} +
+ + +
+ + {/* Right Column - Activity Feed */} +
+ +
+
+
+ ); +} diff --git a/src/pages/Events.tsx b/src/pages/Events.tsx new file mode 100644 index 0000000..0c2e22c --- /dev/null +++ b/src/pages/Events.tsx @@ -0,0 +1,136 @@ +import { Calendar, Ticket, Flag, Search, Filter, Plus } from 'lucide-react'; +import { AppLayout } from '@/components/layout/AppLayout'; +import { cn } from '@/lib/utils'; +import { mockEvents, formatCurrency } from '@/data/mockData'; + +const statusStyles = { + draft: 'bg-muted text-muted-foreground', + published: 'bg-accent/10 text-accent', + live: 'bg-success/10 text-success', + completed: 'bg-muted text-muted-foreground', + cancelled: 'bg-error/10 text-error', + flagged: 'bg-error/10 text-error', +}; + +export default function Events() { + return ( + + {/* Quick Stats */} +
+
+
+
+ +
+
+

43

+

Live Events

+
+
+
+
+
+
+ +
+
+

12.8K

+

Tickets Sold Today

+
+
+
+
+
+
+ +
+
+

3

+

Flagged Events

+
+
+
+
+ + {/* Events Table */} +
+
+

All Events

+
+
+ + +
+ + +
+
+ +
+ + + + + + + + + + + + + + {mockEvents.map((event) => ( + + + + + + + + + + ))} + +
EventPartnerDateStatusTicketsRevenueActions
+

{event.title}

+
{event.partnerName} + {event.date.toLocaleDateString('en-IN', { + day: 'numeric', + month: 'short', + year: 'numeric' + })} + + + {event.status.charAt(0).toUpperCase() + event.status.slice(1)} + + + {event.ticketsSold.toLocaleString()} + + {formatCurrency(event.revenue)} + + +
+
+
+
+ ); +} diff --git a/src/pages/Financials.tsx b/src/pages/Financials.tsx new file mode 100644 index 0000000..cd678fb --- /dev/null +++ b/src/pages/Financials.tsx @@ -0,0 +1,115 @@ +import { IndianRupee, TrendingUp, Wallet, ArrowUpRight, ArrowDownRight } from 'lucide-react'; +import { AppLayout } from '@/components/layout/AppLayout'; +import { formatCurrency, mockRevenueData } from '@/data/mockData'; + +export default function Financials() { + const totalRevenue = mockRevenueData.reduce((sum, d) => sum + d.revenue, 0); + const totalPayouts = mockRevenueData.reduce((sum, d) => sum + d.payouts, 0); + const platformFee = totalRevenue - totalPayouts; + + return ( + + {/* Financial Overview */} +
+
+
+
+ +
+
+

{formatCurrency(totalRevenue)}

+

Total Revenue (7d)

+
+
+
+
+
+
+ +
+
+

{formatCurrency(totalPayouts)}

+

Partner Payouts (7d)

+
+
+
+
+
+
+ +
+
+

{formatCurrency(platformFee)}

+

Platform Earnings (7d)

+
+
+
+
+
+
+ +
+
+

{formatCurrency(845000)}

+

Pending Payouts

+
+
+
+
+ + {/* Transaction History */} +
+
+

Recent Transactions

+ +
+ +
+ {[ + { id: '1', type: 'in', title: 'Mumbai Music Festival', partner: 'Music Nights', amount: 489000, date: new Date() }, + { id: '2', type: 'out', title: 'Partner Payout', partner: 'TechConf India', amount: 245000, date: new Date(Date.now() - 1000 * 60 * 60 * 2) }, + { id: '3', type: 'in', title: 'Tech Summit 2024', partner: 'TechConf India', amount: 156000, date: new Date(Date.now() - 1000 * 60 * 60 * 5) }, + { id: '4', type: 'out', title: 'Partner Payout', partner: 'Music Nights', amount: 180000, date: new Date(Date.now() - 1000 * 60 * 60 * 8) }, + { id: '5', type: 'in', title: 'Night Club Party', partner: 'Music Nights', amount: 24000, date: new Date(Date.now() - 1000 * 60 * 60 * 12) }, + ].map((tx) => ( +
+
+
+ {tx.type === 'in' ? ( + + ) : ( + + )} +
+
+

{tx.title}

+

{tx.partner}

+
+
+
+

+ {tx.type === 'in' ? '+' : '-'}{formatCurrency(tx.amount)} +

+

+ {tx.date.toLocaleDateString('en-IN', { + day: 'numeric', + month: 'short', + hour: '2-digit', + minute: '2-digit' + })} +

+
+
+ ))} +
+
+
+ ); +} diff --git a/src/pages/Partners.tsx b/src/pages/Partners.tsx new file mode 100644 index 0000000..15bda71 --- /dev/null +++ b/src/pages/Partners.tsx @@ -0,0 +1,129 @@ +import { Users, UserCheck, AlertTriangle, Search, Filter } from 'lucide-react'; +import { AppLayout } from '@/components/layout/AppLayout'; +import { cn } from '@/lib/utils'; +import { mockPartners, formatCurrency } from '@/data/mockData'; + +export default function Partners() { + return ( + + {/* Quick Stats */} +
+
+
+
+ +
+
+

156

+

Total Partners

+
+
+
+
+
+
+ +
+
+

12

+

Pending KYC

+
+
+
+
+
+
+ +
+
+

2

+

Stripe Issues

+
+
+
+
+ + {/* Partners Table */} +
+
+

All Partners

+
+
+ + +
+ +
+
+ +
+ + + + + + + + + + + + + {mockPartners.map((partner) => ( + + + + + + + + + ))} + +
PartnerKYC StatusStripeRevenueEventsActions
+
+

{partner.name}

+

{partner.email}

+
+
+ + {partner.kycStatus.charAt(0).toUpperCase() + partner.kycStatus.slice(1)} + + + + {partner.stripeStatus.charAt(0).toUpperCase() + partner.stripeStatus.slice(1)} + + + {formatCurrency(partner.totalRevenue)} + + {partner.eventsCount} + + +
+
+
+
+ ); +} diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx new file mode 100644 index 0000000..a80f1ba --- /dev/null +++ b/src/pages/Settings.tsx @@ -0,0 +1,112 @@ +import { Settings as SettingsIcon, Bell, Shield, Palette, Globe, Database } from 'lucide-react'; +import { AppLayout } from '@/components/layout/AppLayout'; +import { cn } from '@/lib/utils'; + +const settingsSections = [ + { + icon: Bell, + title: 'Notifications', + description: 'Configure email and push notification preferences', + status: 'Enabled', + }, + { + icon: Shield, + title: 'Security', + description: 'Two-factor authentication and session management', + status: '2FA Active', + }, + { + icon: Palette, + title: 'Appearance', + description: 'Theme, colors, and display settings', + status: 'Light Mode', + }, + { + icon: Globe, + title: 'Localization', + description: 'Language, timezone, and currency settings', + status: 'INR / IST', + }, + { + icon: Database, + title: 'Data & Export', + description: 'Export data and manage backups', + status: 'Last backup: Today', + }, +]; + +export default function Settings() { + return ( + + {/* Settings Header Card */} +
+
+
+ +
+
+

Platform Configuration

+

Manage your Eventify backoffice settings

+
+
+
+ + {/* Settings Grid */} +
+ {settingsSections.map((section) => ( + + ))} +
+ + {/* API Settings */} +
+

API Configuration

+
+
+ + +
+
+ + +
+
+
+
+ ); +} diff --git a/src/pages/Users.tsx b/src/pages/Users.tsx new file mode 100644 index 0000000..c9bf537 --- /dev/null +++ b/src/pages/Users.tsx @@ -0,0 +1,144 @@ +import { User, Shield, UserCheck, Search, Plus } from 'lucide-react'; +import { AppLayout } from '@/components/layout/AppLayout'; +import { cn } from '@/lib/utils'; + +const mockUsers = [ + { id: '1', name: 'Arjun Sharma', email: 'arjun@eventify.in', role: 'admin', status: 'active', lastActive: new Date() }, + { id: '2', name: 'Priya Patel', email: 'priya@eventify.in', role: 'support', status: 'active', lastActive: new Date(Date.now() - 1000 * 60 * 30) }, + { id: '3', name: 'Rahul Kumar', email: 'rahul@eventify.in', role: 'viewer', status: 'inactive', lastActive: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3) }, +]; + +export default function Users() { + return ( + + {/* Quick Stats */} +
+
+
+
+ +
+
+

8

+

Total Users

+
+
+
+
+
+
+ +
+
+

3

+

Admins

+
+
+
+
+
+
+ +
+
+

6

+

Active Now

+
+
+
+
+ + {/* Users Table */} +
+
+

Platform Users

+
+
+ + +
+ +
+
+ +
+ + + + + + + + + + + + {mockUsers.map((user) => ( + + + + + + + + ))} + +
UserRoleStatusLast ActiveActions
+
+
+ + {user.name.split(' ').map(n => n[0]).join('')} + +
+
+

{user.name}

+

{user.email}

+
+
+
+ + {user.role} + + + + + {user.status === 'active' ? 'Active' : 'Inactive'} + + + {user.lastActive.toLocaleDateString('en-IN', { + day: 'numeric', + month: 'short', + hour: '2-digit', + minute: '2-digit' + })} + + +
+
+
+
+ ); +} diff --git a/src/types/dashboard.ts b/src/types/dashboard.ts new file mode 100644 index 0000000..546ed04 --- /dev/null +++ b/src/types/dashboard.ts @@ -0,0 +1,68 @@ +// Dashboard Types - API Ready Interfaces + +export interface DashboardMetrics { + totalRevenue: number; + revenueGrowth: number; + activePartners: number; + pendingPartners: number; + liveEvents: number; + eventsToday: number; + ticketSales: number; + ticketGrowth: number; +} + +export interface ActionItem { + id: string; + type: 'kyc' | 'flagged' | 'payout' | 'stripe'; + count: number; + title: string; + description: string; + href: string; + priority: 'high' | 'medium' | 'low'; +} + +export interface ActivityItem { + id: string; + type: 'partner' | 'event' | 'payout' | 'user'; + title: string; + description: string; + timestamp: Date; + status: 'success' | 'pending' | 'warning' | 'error'; +} + +export interface RevenueDataPoint { + day: string; + revenue: number; + payouts: number; +} + +export interface Partner { + id: string; + name: string; + email: string; + kycStatus: 'pending' | 'approved' | 'rejected'; + stripeStatus: 'connected' | 'pending' | 'failed'; + totalRevenue: number; + eventsCount: number; + createdAt: Date; +} + +export interface Event { + id: string; + title: string; + partnerId: string; + partnerName: string; + date: Date; + status: 'draft' | 'published' | 'live' | 'completed' | 'cancelled' | 'flagged'; + ticketsSold: number; + revenue: number; +} + +export interface User { + id: string; + name: string; + email: string; + role: 'admin' | 'support' | 'viewer'; + status: 'active' | 'inactive'; + lastActive: Date; +} diff --git a/tailwind.config.ts b/tailwind.config.ts index a1edb69..e258866 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -57,33 +57,66 @@ export default { border: "hsl(var(--sidebar-border))", ring: "hsl(var(--sidebar-ring))", }, + // Neumorphic theme colors + neu: { + base: "hsl(var(--neu-base))", + surface: "hsl(var(--neu-surface))", + raised: "hsl(var(--neu-raised))", + inset: "hsl(var(--neu-inset))", + }, + // Brand colors + "deep-blue": "hsl(var(--deep-blue))", + "royal-blue": "hsl(var(--royal-blue))", + "ocean-blue": "hsl(var(--ocean-blue))", + "sky-blue": "hsl(var(--sky-blue))", + "ice-blue": "hsl(var(--ice-blue))", + // Semantic + success: { + DEFAULT: "hsl(var(--success))", + foreground: "hsl(var(--success-foreground))", + }, + warning: { + DEFAULT: "hsl(var(--warning))", + foreground: "hsl(var(--warning-foreground))", + }, + error: { + DEFAULT: "hsl(var(--error))", + foreground: "hsl(var(--error-foreground))", + }, }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", + xl: "1rem", + "2xl": "1.25rem", + }, + boxShadow: { + // Neumorphic shadows + "neu": "6px 6px 12px hsl(var(--neu-inset)), -6px -6px 12px hsl(var(--neu-raised))", + "neu-sm": "4px 4px 8px hsl(var(--neu-inset)), -4px -4px 8px hsl(var(--neu-raised))", + "neu-lg": "8px 8px 16px hsl(var(--neu-inset)), -8px -8px 16px hsl(var(--neu-raised))", + "neu-inset": "inset 3px 3px 6px hsl(var(--neu-inset)), inset -3px -3px 6px hsl(var(--neu-raised))", + "neu-inset-sm": "inset 2px 2px 4px hsl(var(--neu-inset)), inset -2px -2px 4px hsl(var(--neu-raised))", }, keyframes: { "accordion-down": { - from: { - height: "0", - }, - to: { - height: "var(--radix-accordion-content-height)", - }, + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, }, "accordion-up": { - from: { - height: "var(--radix-accordion-content-height)", - }, - to: { - height: "0", - }, + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + "pulse-soft": { + "0%, 100%": { opacity: "1" }, + "50%": { opacity: "0.7" }, }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", + "pulse-soft": "pulse-soft 2s ease-in-out infinite", }, }, },