@@ -6,7 +6,6 @@ import { useMemo, useState } from "react";
66import Image from "next/image" ;
77import Link from "next/link" ;
88import CodevBadge from "@/components/CodevBadge" ;
9- import { getTotalCodevPoints } from "@/utils/codev-qualification" ;
109import {
1110 AnimatePresence ,
1211 motion ,
@@ -32,32 +31,38 @@ interface Props {
3231 color : string ;
3332}
3433
35- const STATUS_CONFIG = {
34+ const STATUS_CONFIG : Record < InternalStatus , { label : string ; className : string } > = {
3635 TRAINING : {
3736 label : "Training" ,
38- className : "bg-yellow-500/20 backdrop-blur-sm text-yellow-200 border border-yellow-500/30 dark:bg-yellow-500/10 dark:text-yellow-300" ,
37+ className :
38+ "bg-yellow-500/20 backdrop-blur-sm text-yellow-200 border border-yellow-500/30 dark:bg-yellow-500/10 dark:text-yellow-300" ,
3939 } ,
4040 GRADUATED : {
4141 label : "Graduated" ,
42- className : "bg-green-500/20 backdrop-blur-sm text-green-200 border border-green-500/30 dark:bg-green-500/10 dark:text-green-300" ,
42+ className :
43+ "bg-green-500/20 backdrop-blur-sm text-green-200 border border-green-500/30 dark:bg-green-500/10 dark:text-green-300" ,
4344 } ,
4445 INACTIVE : {
4546 label : "Inactive" ,
46- className : "bg-gray-500/20 backdrop-blur-sm text-gray-200 border border-gray-500/30 dark:bg-gray-500/10 dark:text-gray-300" ,
47+ className :
48+ "bg-gray-500/20 backdrop-blur-sm text-gray-200 border border-gray-500/30 dark:bg-gray-500/10 dark:text-gray-300" ,
4749 } ,
4850 MENTOR : {
4951 label : "Mentor" ,
50- className : "bg-purple-500/20 backdrop-blur-sm text-purple-200 border border-purple-500/30 dark:bg-purple-500/10 dark:text-purple-300" ,
52+ className :
53+ "bg-purple-500/20 backdrop-blur-sm text-purple-200 border border-purple-500/30 dark:bg-purple-500/10 dark:text-purple-300" ,
5154 } ,
5255 ADMIN : {
5356 label : "Admin" ,
54- className : "bg-blue-500/20 backdrop-blur-sm text-blue-200 border border-blue-500/30 dark:bg-blue-500/10 dark:text-blue-300" ,
57+ className :
58+ "bg-blue-500/20 backdrop-blur-sm text-blue-200 border border-blue-500/30 dark:bg-blue-500/10 dark:text-blue-300" ,
5559 } ,
5660 DEPLOYED : {
5761 label : "Deployed" ,
58- className : "bg-indigo-500/20 backdrop-blur-sm text-indigo-200 border border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-300" ,
62+ className :
63+ "bg-indigo-500/20 backdrop-blur-sm text-indigo-200 border border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-300" ,
5964 } ,
60- } as const ;
65+ } ;
6166
6267const CodevCard = ( { codev, color } : Props ) => {
6368 const [ hovered , setHovered ] = useState ( false ) ;
@@ -78,10 +83,13 @@ const CodevCard = ({ codev, color }: Props) => {
7883 } ;
7984
8085 const internalStatus = codev . internal_status || "MENTOR" ;
81- const statusConfig = STATUS_CONFIG [ internalStatus as keyof typeof STATUS_CONFIG ] || STATUS_CONFIG . MENTOR ;
86+ const statusConfig =
87+ STATUS_CONFIG [ internalStatus as InternalStatus ] || STATUS_CONFIG . MENTOR ;
8288
8389 const filteredLevel = useMemo ( ( ) => {
84- return codev . level && codev . codev_points && Array . isArray ( codev . codev_points )
90+ return codev . level &&
91+ codev . codev_points &&
92+ Array . isArray ( codev . codev_points )
8593 ? Object . fromEntries (
8694 Object . entries ( codev . level )
8795 . filter ( ( [ skillCategoryId , levelValue ] ) => {
@@ -93,79 +101,143 @@ const CodevCard = ({ codev, color }: Props) => {
93101 ) ;
94102 } )
95103 . sort ( ( [ skillCategoryIdA ] , [ skillCategoryIdB ] ) => {
96- const pointsA = codev . codev_points ! . find ( ( point ) => point ?. skill_category_id === skillCategoryIdA ) ?. points || 0 ;
97- const pointsB = codev . codev_points ! . find ( ( point ) => point ?. skill_category_id === skillCategoryIdB ) ?. points || 0 ;
104+ const pointsA =
105+ codev . codev_points ! . find (
106+ ( point ) => point ?. skill_category_id === skillCategoryIdA ,
107+ ) ?. points || 0 ;
108+ const pointsB =
109+ codev . codev_points ! . find (
110+ ( point ) => point ?. skill_category_id === skillCategoryIdB ,
111+ ) ?. points || 0 ;
98112 return pointsB - pointsA ;
99113 } ) ,
100114 )
101115 : { } ;
102116 } , [ codev . level , codev . codev_points ] ) ;
103117
104- const totalPoints = getTotalCodevPoints ( codev . codev_points ) ;
105-
106118 return (
107- < div className = "h-64" onMouseEnter = { ( ) => setHovered ( true ) } onMouseLeave = { ( ) => setHovered ( false ) } >
108- < div onClick = { ( ) => window . location . href = `https://www.codebility.tech/profiles/${ codev . id } ` } className = "group relative flex h-full w-full flex-col items-center justify-between rounded-lg border border-white/20 bg-white/10 p-4 shadow-2xl backdrop-blur-md transition-all duration-300 cursor-pointer hover:bg-white/20 hover:scale-[1.02] dark:border-white/10 dark:bg-white/5 dark:hover:bg-white/10" >
119+ < div
120+ className = "h-64"
121+ onMouseEnter = { ( ) => setHovered ( true ) }
122+ onMouseLeave = { ( ) => setHovered ( false ) }
123+ >
124+ < div
125+ onClick = { ( ) => window . location . href = `https://www.codebility.tech/profiles/${ codev . id } ` }
126+ className = "group relative flex h-full w-full flex-col items-center justify-between rounded-lg border border-white/20 bg-white/10 p-4 shadow-2xl backdrop-blur-md transition-all duration-300 cursor-pointer hover:bg-white/20 hover:scale-[1.02] dark:border-white/10 dark:bg-white/5 dark:hover:bg-white/10"
127+ >
128+ { /* Background decoration */ }
109129 < div className = "from-customBlue-50/30 dark:from-customBlue-950/10 absolute inset-0 bg-gradient-to-br to-purple-50/30 opacity-0 transition-opacity duration-300 group-hover:opacity-100 dark:to-purple-950/10" />
110130 < div className = "absolute -right-4 -top-4 h-32 w-32 rounded-full bg-gradient-to-br from-yellow-400/10 to-orange-400/10 opacity-0 blur-2xl transition-opacity duration-500 group-hover:opacity-100" />
131+
111132 < div className = "grid w-full grid-cols-4 gap-2" >
112133 < div className = "col-span-1" />
113134 < div className = "relative col-span-2 flex justify-center" >
114- < div className = "absolute -top-2 -right-2 z-20" >
115- < TooltipProvider >
116- < Tooltip delayDuration = { 100 } >
117- < TooltipTrigger asChild >
118- < div className = "flex items-center gap-1 rounded-full border border-yellow-500/30 bg-gradient-to-br from-yellow-500/20 to-orange-500/20 px-2.5 py-1 shadow-lg backdrop-blur-sm transition-all duration-300 hover:scale-110 hover:from-yellow-500/30 hover:to-orange-500/30" >
119- < span className = "text-xs font-bold text-yellow-200" > ⭐</ span >
120- < span className = "text-xs font-bold text-yellow-200" > { totalPoints } </ span >
121- </ div >
122- </ TooltipTrigger >
123- < TooltipContent className = "border border-white/20 bg-white/10 px-3 py-2 text-xs text-white backdrop-blur-md dark:border-white/10 dark:bg-white/5" >
124- < div className = "flex flex-col gap-1" >
125- < p className = "font-semibold" > Total Skill Points</ p >
126- < p className = "text-gray-200" > Qualification: ≥100 points</ p >
127- </ div >
128- </ TooltipContent >
129- </ Tooltip >
130- </ TooltipProvider >
131- </ div >
132- < Image alt = { `${ codev . first_name } Avatar` } src = { codev . image_url && ( codev . image_url . startsWith ( "http" ) || codev . image_url . startsWith ( "/" ) ) ? codev . image_url : "/assets/svgs/icon-codebility-black.svg" } width = { 70 } height = { 70 } className = { `${ color } h-[70px] w-[70px] rounded-full bg-cover object-cover p-0.5` } onMouseMove = { handleMouseMove } onError = { ( e ) => { console . error ( `Failed to load image for ${ codev . first_name } : ${ codev . image_url } ` ) ; e . currentTarget . src = "/assets/svgs/icon-codebility-black.svg" ; } } />
135+ < Image
136+ alt = { `${ codev . first_name } Avatar` }
137+ src = {
138+ codev . image_url &&
139+ ( codev . image_url . startsWith ( "http" ) ||
140+ codev . image_url . startsWith ( "/" ) )
141+ ? codev . image_url
142+ : "/assets/svgs/icon-codebility-black.svg"
143+ }
144+ width = { 70 }
145+ height = { 70 }
146+ className = { `${ color } h-[70px] w-[70px] rounded-full bg-cover object-cover p-0.5` }
147+ onMouseMove = { handleMouseMove }
148+ onError = { ( e ) => {
149+ console . error (
150+ `Failed to load image for ${ codev . first_name } : ${ codev . image_url } ` ,
151+ ) ;
152+ e . currentTarget . src = "/assets/svgs/icon-codebility-black.svg" ;
153+ } }
154+ />
133155 < AnimatePresence >
134156 { hovered && internalStatus && (
135- < motion . div initial = { { opacity : 0 , y : 20 , scale : 0.6 } } animate = { { opacity : 1 , y : 0 , scale : 1 , transition : { type : "spring" , stiffness : 260 , damping : 10 } } } exit = { { opacity : 0 , y : 20 , scale : 0.6 } } style = { { translateX : translateX , rotate : rotate , whiteSpace : "nowrap" } } className = { cn ( "absolute -top-8 right-1/2 z-50 flex -translate-x-1/2 transform flex-col items-center justify-center rounded-lg px-4 py-2 shadow-xl" , statusConfig . className ) } >
136- < div className = "relative z-30 text-base" > { statusConfig . label } </ div >
157+ < motion . div
158+ initial = { { opacity : 0 , y : 20 , scale : 0.6 } }
159+ animate = { {
160+ opacity : 1 ,
161+ y : 0 ,
162+ scale : 1 ,
163+ transition : {
164+ type : "spring" ,
165+ stiffness : 260 ,
166+ damping : 10 ,
167+ } ,
168+ } }
169+ exit = { { opacity : 0 , y : 20 , scale : 0.6 } }
170+ style = { {
171+ translateX : translateX ,
172+ rotate : rotate ,
173+ whiteSpace : "nowrap" ,
174+ } }
175+ className = { cn (
176+ "absolute -top-8 right-1/2 z-50 flex -translate-x-1/2 transform flex-col items-center justify-center rounded-lg px-4 py-2 shadow-xl" ,
177+ statusConfig . className ,
178+ ) }
179+ >
180+ < div className = "relative z-30 text-base" >
181+ { statusConfig . label }
182+ </ div >
137183 </ motion . div >
138184 ) }
139185 </ AnimatePresence >
140186 < div className = "absolute bottom-[1px] right-[1px]" >
141- < p className = { cn ( "rounded-full border-2 p-2 text-[9px]" , codev . availability_status ? "border-green-600 bg-green-500" : "border-red-600 bg-red-500" ) } > </ p >
187+ < p
188+ className = { cn (
189+ "rounded-full border-2 p-2 text-[9px]" ,
190+ codev . availability_status
191+ ? "border-green-600 bg-green-500"
192+ : "border-red-600 bg-red-500" ,
193+ ) }
194+ > </ p >
142195 </ div >
143196 </ div >
144197 < div className = "col-span-1 flex justify-end" >
145198 < TooltipProvider >
146199 < Tooltip delayDuration = { 100 } >
147200 < TooltipTrigger asChild >
148201 < Link href = { `/profiles/${ codev . id } ` } target = "_blank" onClick = { ( e ) => e . stopPropagation ( ) } >
149- < Button variant = "outline" size = "icon" className = "h-8 w-8 rounded-full border-white/30 bg-white/20 backdrop-blur-sm transition-all duration-300 hover:bg-white/30 dark:border-white/20 dark:bg-white/10 dark:hover:bg-white/20" >
202+ < Button
203+ variant = "outline"
204+ size = "icon"
205+ className = "h-8 w-8 rounded-full border-white/30 bg-white/20 backdrop-blur-sm transition-all duration-300 hover:bg-white/30 dark:border-white/20 dark:bg-white/10 dark:hover:bg-white/20"
206+ >
150207 < BookOpenIcon className = "h-3.5 w-3.5" />
151208 </ Button >
152209 </ Link >
153210 </ TooltipTrigger >
154- < TooltipContent className = "border border-white/20 bg-white/10 px-2 py-1 text-xs text-white backdrop-blur-md dark:border-white/10 dark:bg-white/5" > Read Bio</ TooltipContent >
211+ < TooltipContent className = "border border-white/20 bg-white/10 px-2 py-1 text-xs text-white backdrop-blur-md dark:border-white/10 dark:bg-white/5" >
212+ Read Bio
213+ </ TooltipContent >
155214 </ Tooltip >
156215 </ TooltipProvider >
157216 </ div >
158217 </ div >
159218 < div className = "flex flex-col items-center gap-0.5" >
160219 < div className = "flex flex-col gap-1 text-center" >
161- < p className = "md:text-md relative z-10 text-sm capitalize text-white dark:text-white lg:text-base" > { codev . first_name } { codev . last_name } </ p >
162- { codev . display_position ? ( < p className = "relative z-10 text-sm text-gray-200 dark:text-gray-300" > { codev . display_position } </ p > ) : ( < div className = "relative z-10 text-sm lg:text-base" > </ div > ) }
220+ < p className = "md:text-md relative z-10 text-sm capitalize text-white dark:text-white lg:text-base" >
221+ { codev . first_name } { codev . last_name }
222+ </ p >
223+ { codev . display_position ? (
224+ < p className = "relative z-10 text-sm text-gray-200 dark:text-gray-300" >
225+ { codev . display_position }
226+ </ p >
227+ ) : (
228+ < div className = "relative z-10 text-sm lg:text-base" > </ div >
229+ ) }
163230 </ div >
164231 </ div >
165232 < div className = "flex min-h-[20px] items-center justify-center" >
166- { filteredLevel && Object . keys ( filteredLevel ) . length > 0 ? ( < CodevBadge level = { filteredLevel } className = "transition-transform group-hover:scale-100" /> ) : null }
233+ { filteredLevel && Object . keys ( filteredLevel ) . length > 0 ? (
234+ < CodevBadge
235+ level = { filteredLevel }
236+ className = "transition-transform group-hover:scale-100"
237+ />
238+ ) : null }
167239 </ div >
168- < CodevHireCodevButton codevId = { codev . id } />
240+ < CodevHireCodevButton codevId = { codev . id } hovered = { hovered } />
169241 </ div >
170242 </ div >
171243 ) ;
0 commit comments