-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathApp.tsx
More file actions
205 lines (186 loc) · 10.4 KB
/
App.tsx
File metadata and controls
205 lines (186 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import React, { useState, useMemo } from 'react';
import HeroSection from './components/HeroSection';
import SuperFanLeaderboard from './components/SuperFanLeaderboard';
import ConcertPreSaleManager from './components/ConcertPreSaleManager';
import AnalyticsGrid from './components/AnalyticsGrid';
import { generateFans, mockArtist, mockConcerts, mockAnalytics } from './services/mockData';
const App: React.FC = () => {
const fans = useMemo(() => generateFans(50), []);
const [activeTab, setActiveTab] = useState<'dashboard' | 'analytics' | 'wallet' | 'fans'>('dashboard');
const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
return (
<div className="flex min-h-screen bg-[#f0f5ff]">
{/* Sidebar - Collapsible */}
<aside className={`${sidebarCollapsed ? 'w-24' : 'w-72'} hidden lg:flex flex-col p-8 backdrop-blur-3xl bg-white/40 border-r border-white/60 sticky top-0 h-screen transition-all duration-300 ease-in-out`}>
<div className={`flex items-center ${sidebarCollapsed ? 'justify-center' : 'gap-3'} mb-16 px-2`}>
<div className="w-12 h-12 rounded-2xl bg-gradient-to-tr from-blue-500 to-indigo-600 flex items-center justify-center text-white shadow-xl shadow-blue-500/20 flex-shrink-0">
<span className="font-black text-2xl">S</span>
</div>
<span className={`text-2xl font-black text-slate-800 tracking-tighter uppercase whitespace-nowrap overflow-hidden transition-all duration-300 ${sidebarCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}>SupaFans</span>
</div>
<nav className="flex-1 space-y-2">
<NavItem
active={activeTab === 'dashboard'}
onClick={() => setActiveTab('dashboard')}
label="Dashboard"
icon={<DashboardIcon />}
collapsed={sidebarCollapsed}
/>
<NavItem
active={activeTab === 'analytics'}
onClick={() => setActiveTab('analytics')}
label="Insights"
icon={<AnalyticsIcon />}
collapsed={sidebarCollapsed}
/>
<NavItem
active={activeTab === 'wallet'}
onClick={() => setActiveTab('wallet')}
label="Royalties"
icon={<WalletIcon />}
collapsed={sidebarCollapsed}
/>
<NavItem
active={activeTab === 'fans'}
onClick={() => setActiveTab('fans')}
label="Community"
icon={<FansIcon />}
collapsed={sidebarCollapsed}
/>
</nav>
{/* Collapse Toggle Button */}
<button
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
className="mt-auto flex items-center justify-center w-full py-4 rounded-2xl text-slate-400 hover:text-slate-600 hover:bg-white/40 transition-all duration-300"
title={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
className={`transition-transform duration-300 ${sidebarCollapsed ? 'rotate-180' : ''}`}
>
<path d="m11 17-5-5 5-5"/>
<path d="m18 17-5-5 5-5"/>
</svg>
</button>
</aside>
{/* Main Content Area */}
<main className="flex-1 px-8 lg:px-12 py-10 pb-24 overflow-x-hidden">
{/* Header Bar */}
<header className="flex items-center justify-between mb-12">
<div className="flex-1 max-w-xl hidden md:block">
<div className="relative group">
<SearchIcon className="absolute left-4 top-1/2 -translate-y-1/2 text-slate-300 w-5 h-5 group-focus-within:text-blue-500 transition-colors" />
<input
type="text"
placeholder="Find superfans, concerts or transactions..."
className="w-full bg-white/60 border border-white/80 rounded-3xl pl-12 pr-6 py-4 text-sm focus:outline-none focus:ring-4 focus:ring-blue-500/10 transition-all shadow-sm"
/>
</div>
</div>
<div className="flex items-center gap-6">
<button className="p-4 rounded-2xl bg-white/60 border border-white/80 shadow-sm text-slate-400 hover:text-blue-500 transition-all">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>
</button>
<div className="flex items-center gap-4 pl-4 border-l border-slate-200">
<div className="text-right">
<p className="text-sm font-black text-slate-800">{mockArtist.name}</p>
<p className="text-[10px] text-blue-500 font-bold uppercase tracking-widest">Creator</p>
</div>
<img src={mockArtist.logo} alt="" className="w-12 h-12 rounded-2xl border-4 border-white shadow-lg object-cover" />
</div>
</div>
</header>
{/* Dynamic Content */}
{activeTab === 'dashboard' ? (
<div className="animate-fadeIn">
<HeroSection artist={mockArtist} nextConcert={mockConcerts[0]} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-10 mt-12">
<div className="lg:col-span-2">
<SuperFanLeaderboard fans={fans} artistName={mockArtist.name} />
</div>
<div className="lg:col-span-1">
<ConcertPreSaleManager concerts={mockConcerts} fans={fans} />
</div>
</div>
<AnalyticsGrid stats={mockAnalytics} />
</div>
) : (
<div className="animate-fadeIn py-10 flex flex-col items-center justify-center text-center min-h-[60vh]">
<div className="w-24 h-24 rounded-[2rem] bg-slate-100 flex items-center justify-center mb-8 border border-white shadow-inner">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" x2="12" y1="8" y2="12"></line><line x1="12" x2="12.01" y1="16" y2="16"></line></svg>
</div>
<h2 className="text-4xl font-black text-slate-800 mb-4 tracking-tighter">Under Construction</h2>
<p className="text-slate-400 max-w-md font-medium">This module is part of the premium artist suite and will be available in the next platform update.</p>
<button
onClick={() => setActiveTab('dashboard')}
className="mt-10 bg-slate-900 text-white px-8 py-4 rounded-2xl font-bold shadow-2xl shadow-slate-300 transition-all hover:scale-105 active:scale-95"
>
Return to Dashboard
</button>
</div>
)}
</main>
<style>{`
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fadeIn {
animation: fadeIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #e2e8f0;
border-radius: 10px;
}
`}</style>
</div>
);
};
interface NavItemProps {
active: boolean;
label: string;
icon: React.ReactNode;
onClick: () => void;
collapsed?: boolean;
}
const NavItem: React.FC<NavItemProps> = ({ active, label, icon, onClick, collapsed = false }) => (
<button
onClick={onClick}
title={collapsed ? label : undefined}
className={`w-full flex items-center ${collapsed ? 'justify-center' : 'gap-4'} px-5 py-4 rounded-2xl transition-all duration-300 ${
active
? 'bg-white shadow-[0_8px_20px_rgba(0,0,0,0.03)] text-blue-600 border border-slate-50 font-bold'
: 'text-slate-400 hover:text-slate-600 hover:bg-white/40 font-medium'
}`}
>
<span className={`transition-colors flex-shrink-0 ${active ? 'text-blue-600' : 'text-slate-300'}`}>{icon}</span>
<span className={`text-sm whitespace-nowrap overflow-hidden transition-all duration-300 ${collapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}>{label}</span>
</button>
);
const DashboardIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><rect width="7" height="9" x="3" y="3" rx="1"></rect><rect width="7" height="5" x="14" y="3" rx="1"></rect><rect width="7" height="9" x="14" y="12" rx="1"></rect><rect width="7" height="5" x="3" y="16" rx="1"></rect></svg>
);
const AnalyticsIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 3v18h18"></path><path d="m19 9-5 5-4-4-3 3"></path></svg>
);
const WalletIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1"></path><path d="M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4"></path></svg>
);
const FansIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
);
const SearchIcon = ({ className }: { className?: string }) => (
<svg className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="8"></circle><path d="m21 21-4.3-4.3"></path></svg>
);
export default App;