1+ import { useQuery } from "convex/react" ;
2+ import { api } from "../../../../convex/_generated/api" ;
3+ import { Activity , Code2 , Star , Timer , TrendingUp , Trophy , UserIcon , Zap } from "lucide-react" ;
4+ import { motion } from "framer-motion" ;
5+ import { Id } from "../../../../convex/_generated/dataModel" ;
6+
7+ import { UserResource } from "@clerk/types" ;
8+
9+ interface ProfileHeaderProps {
10+ userStats : {
11+ totalExecutions : number ;
12+ languagesCount : number ;
13+ languages : string [ ] ;
14+ last24Hours : number ;
15+ favoriteLanguage : string ;
16+ languageStats : Record < string , number > ;
17+ mostStarredLanguage : string ;
18+ } ;
19+ userData : {
20+ _id : Id < "users" > ;
21+ _creationTime : number ;
22+ proSince ?: number | undefined ;
23+ lemonSqueezyCustomerId ?: string | undefined ;
24+ lemonSqueezyOrderId ?: string | undefined ;
25+ name : string ;
26+ userId : string ;
27+ email : string ;
28+ isPro : boolean ;
29+ } ;
30+ user : UserResource ;
31+ }
32+
33+ function ProfileHeader ( { userStats, userData, user } : ProfileHeaderProps ) {
34+ const starredSnippets = useQuery ( api . snippets . getStarredSnippets ) ;
35+ const STATS = [
36+ {
37+ label : "Code Executions" ,
38+ value : userStats ?. totalExecutions ?? 0 ,
39+ icon : Activity ,
40+ color : "from-blue-500 to-cyan-500" ,
41+ gradient : "group-hover:via-blue-400" ,
42+ description : "Total code runs" ,
43+ metric : {
44+ label : "Last 24h" ,
45+ value : userStats ?. last24Hours ?? 0 ,
46+ icon : Timer ,
47+ } ,
48+ } ,
49+ {
50+ label : "Starred Snippets" ,
51+ value : starredSnippets ?. length ?? 0 ,
52+ icon : Star ,
53+ color : "from-yellow-500 to-orange-500" ,
54+ gradient : "group-hover:via-yellow-400" ,
55+ description : "Saved for later" ,
56+ metric : {
57+ label : "Most starred" ,
58+ value : userStats ?. mostStarredLanguage ?? "N/A" ,
59+ icon : Trophy ,
60+ } ,
61+ } ,
62+ {
63+ label : "Languages Used" ,
64+ value : userStats ?. languagesCount ?? 0 ,
65+ icon : Code2 ,
66+ color : "from-purple-500 to-pink-500" ,
67+ gradient : "group-hover:via-purple-400" ,
68+ description : "Different languages" ,
69+ metric : {
70+ label : "Most used" ,
71+ value : userStats ?. favoriteLanguage ?? "N/A" ,
72+ icon : TrendingUp ,
73+ } ,
74+ } ,
75+ ] ;
76+
77+ return (
78+ < div
79+ className = "relative mb-8 bg-gradient-to-br from-[#12121a] to-[#1a1a2e] rounded-2xl p-8 border
80+ border-gray-800/50 overflow-hidden"
81+ >
82+ < div className = "absolute inset-0 bg-grid-white/[0.02] bg-[size:32px]" />
83+ < div className = "relative flex items-center gap-8" >
84+ < div className = "relative group" >
85+ < div
86+ className = "absolute inset-0 bg-gradient-to-r from-blue-500 to-purple-600 rounded-full
87+ blur-xl opacity-50 group-hover:opacity-75 transition-opacity"
88+ />
89+ < img
90+ src = { user . imageUrl }
91+ alt = "Profile"
92+ className = "w-24 h-24 rounded-full border-4 border-gray-800/50 relative z-10 group-hover:scale-105 transition-transform"
93+ />
94+ { userData . isPro && (
95+ < div
96+ className = "absolute -top-2 -right-2 bg-gradient-to-r from-purple-500 to-purple-600 p-2
97+ rounded-full z-20 shadow-lg animate-pulse"
98+ >
99+ < Zap className = "w-4 h-4 text-white" />
100+ </ div >
101+ ) }
102+ </ div >
103+ < div >
104+ < div className = "flex items-center gap-3 mb-2" >
105+ < h1 className = "text-3xl font-bold text-white" > { userData . name } </ h1 >
106+ { userData . isPro && (
107+ < span className = "px-3 py-1 bg-purple-500/10 text-purple-400 rounded-full text-sm font-medium" >
108+ Pro Member
109+ </ span >
110+ ) }
111+ </ div >
112+ < p className = "text-gray-400 flex items-center gap-2" >
113+ < UserIcon className = "w-4 h-4" />
114+ { userData . email }
115+ </ p >
116+ </ div >
117+ </ div >
118+
119+ { /* Stats Cards */ }
120+ < div className = "grid grid-cols-1 md:grid-cols-3 gap-6 mt-8" >
121+ { STATS . map ( ( stat , index ) => (
122+ < motion . div
123+ initial = { { opacity : 0 , y : 20 } }
124+ animate = { { opacity : 1 , y : 0 } }
125+ transition = { { duration : 0.5 , delay : index * 0.1 } }
126+ key = { index }
127+ className = "group relative bg-gradient-to-br from-black/40 to-black/20 rounded-2xl overflow-hidden"
128+ >
129+ { /* Glow effect */ }
130+ < div
131+ className = { `absolute inset-0 bg-gradient-to-r ${ stat . color } opacity-0 group-hover:opacity-10 transition-all
132+ duration-500 ${ stat . gradient } ` }
133+ />
134+
135+ { /* Content */ }
136+ < div className = "relative p-6" >
137+ < div className = "flex items-start justify-between mb-4" >
138+ < div >
139+ < div className = "flex items-center gap-2 mb-1" >
140+ < span className = "text-sm font-medium text-gray-400" > { stat . description } </ span >
141+ </ div >
142+ < h3 className = "text-2xl font-bold text-white tracking-tight" >
143+ { typeof stat . value === "number" ? stat . value . toLocaleString ( ) : stat . value }
144+ </ h3 >
145+ < p className = "text-sm text-gray-400 mt-1" > { stat . label } </ p >
146+ </ div >
147+ < div className = { `p-3 rounded-xl bg-gradient-to-br ${ stat . color } bg-opacity-10` } >
148+ < stat . icon className = "w-5 h-5 text-white" />
149+ </ div >
150+ </ div >
151+
152+ { /* Additional metric */ }
153+ < div className = "flex items-center gap-2 pt-4 border-t border-gray-800/50" >
154+ < stat . metric . icon className = "w-4 h-4 text-gray-500" />
155+ < span className = "text-sm text-gray-400" > { stat . metric . label } :</ span >
156+ < span className = "text-sm font-medium text-white" > { stat . metric . value } </ span >
157+ </ div >
158+ </ div >
159+
160+ { /* Interactive hover effect */ }
161+ < div className = "absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent -translate-x-full group-hover:translate-x-full duration-1000 transition-transform" />
162+ </ motion . div >
163+ ) ) }
164+ </ div >
165+ </ div >
166+ ) ;
167+ }
168+ export default ProfileHeader ;
0 commit comments