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