1+ // src/components/dashboard/LeaderBoard/PRListModal.tsx
2+ import React from "react" ;
3+ import { motion , AnimatePresence } from "framer-motion" ;
4+ import { FaTimes , FaExternalLinkAlt , FaGithub } from "react-icons/fa" ;
5+ import { useColorMode } from "@docusaurus/theme-common" ;
6+
7+ interface PRDetails {
8+ title : string ;
9+ url : string ;
10+ mergedAt : string ;
11+ repoName : string ;
12+ number : number ;
13+ }
14+
15+ interface Contributor {
16+ username : string ;
17+ avatar : string ;
18+ profile : string ;
19+ points : number ;
20+ prs : number ;
21+ prDetails ?: PRDetails [ ] ;
22+ }
23+
24+ interface PRListModalProps {
25+ contributor : Contributor | null ;
26+ isOpen : boolean ;
27+ onClose : ( ) => void ;
28+ }
29+
30+ export default function PRListModal ( { contributor, isOpen, onClose } : PRListModalProps ) : JSX . Element | null {
31+ const { colorMode } = useColorMode ( ) ;
32+ const isDark = colorMode === "dark" ;
33+
34+ if ( ! contributor ) return null ;
35+
36+ const formatDate = ( dateString : string ) => {
37+ const date = new Date ( dateString ) ;
38+ return date . toLocaleDateString ( 'en-US' , {
39+ year : 'numeric' ,
40+ month : 'short' ,
41+ day : 'numeric'
42+ } ) ;
43+ } ;
44+
45+ const handleBackdropClick = ( e : React . MouseEvent ) => {
46+ if ( e . target === e . currentTarget ) {
47+ onClose ( ) ;
48+ }
49+ } ;
50+
51+ const handleKeyDown = ( e : React . KeyboardEvent ) => {
52+ if ( e . key === 'Escape' ) {
53+ onClose ( ) ;
54+ }
55+ } ;
56+
57+ return (
58+ < AnimatePresence >
59+ { isOpen && (
60+ < motion . div
61+ className = { `pr-modal-backdrop ${ isDark ? "dark" : "light" } ` }
62+ initial = { { opacity : 0 } }
63+ animate = { { opacity : 1 } }
64+ exit = { { opacity : 0 } }
65+ onClick = { handleBackdropClick }
66+ onKeyDown = { handleKeyDown }
67+ tabIndex = { 0 }
68+ role = "dialog"
69+ aria-modal = "true"
70+ aria-labelledby = "pr-modal-title"
71+ >
72+ < motion . div
73+ className = { `pr-modal-container ${ isDark ? "dark" : "light" } ` }
74+ initial = { { opacity : 0 , scale : 0.9 , y : 20 } }
75+ animate = { { opacity : 1 , scale : 1 , y : 0 } }
76+ exit = { { opacity : 0 , scale : 0.9 , y : 20 } }
77+ transition = { { type : "spring" , damping : 25 , stiffness : 300 } }
78+ >
79+ { /* Modal Header */ }
80+ < div className = { `pr-modal-header ${ isDark ? "dark" : "light" } ` } >
81+ < div className = "pr-modal-user-info" >
82+ < img
83+ src = { contributor . avatar }
84+ alt = { contributor . username }
85+ className = "pr-modal-avatar"
86+ />
87+ < div >
88+ < h2 id = "pr-modal-title" className = { `pr-modal-title ${ isDark ? "dark" : "light" } ` } >
89+ { contributor . username } 's Pull Requests
90+ </ h2 >
91+ < p className = { `pr-modal-subtitle ${ isDark ? "dark" : "light" } ` } >
92+ { contributor . prs } merged PR{ contributor . prs !== 1 ? 's' : '' } • { contributor . points } points
93+ </ p >
94+ </ div >
95+ </ div >
96+ < button
97+ className = { `pr-modal-close ${ isDark ? "dark" : "light" } ` }
98+ onClick = { onClose }
99+ aria-label = "Close modal"
100+ >
101+ < FaTimes />
102+ </ button >
103+ </ div >
104+
105+ { /* Modal Body */ }
106+ < div className = { `pr-modal-body ${ isDark ? "dark" : "light" } ` } >
107+ { contributor . prDetails && contributor . prDetails . length > 0 ? (
108+ < div className = "pr-list" >
109+ { contributor . prDetails . map ( ( pr , index ) => (
110+ < motion . div
111+ key = { `${ pr . repoName } -${ pr . number } ` }
112+ className = { `pr-item ${ isDark ? "dark" : "light" } ` }
113+ initial = { { opacity : 0 , x : - 20 } }
114+ animate = { { opacity : 1 , x : 0 } }
115+ transition = { { delay : index * 0.05 } }
116+ >
117+ < div className = "pr-item-header" >
118+ < h3 className = { `pr-item-title ${ isDark ? "dark" : "light" } ` } >
119+ { pr . title }
120+ </ h3 >
121+ < div className = "pr-item-actions" >
122+ < a
123+ href = { pr . url }
124+ target = "_blank"
125+ rel = "noopener noreferrer"
126+ className = { `pr-item-link ${ isDark ? "dark" : "light" } ` }
127+ aria-label = { `Open PR #${ pr . number } in GitHub` }
128+ >
129+ < FaExternalLinkAlt />
130+ </ a >
131+ </ div >
132+ </ div >
133+ < div className = "pr-item-meta" >
134+ < span className = { `pr-item-repo ${ isDark ? "dark" : "light" } ` } >
135+ < FaGithub />
136+ { pr . repoName }
137+ </ span >
138+ < span className = { `pr-item-number ${ isDark ? "dark" : "light" } ` } >
139+ #{ pr . number }
140+ </ span >
141+ < span className = { `pr-item-date ${ isDark ? "dark" : "light" } ` } >
142+ Merged on { formatDate ( pr . mergedAt ) }
143+ </ span >
144+ </ div >
145+ </ motion . div >
146+ ) ) }
147+ </ div >
148+ ) : (
149+ < div className = { `pr-empty-state ${ isDark ? "dark" : "light" } ` } >
150+ < FaGithub className = "pr-empty-icon" />
151+ < p > No pull request details available</ p >
152+ < p className = "pr-empty-subtitle" >
153+ PR details might not be loaded yet or this contributor has no merged PRs.
154+ </ p >
155+ </ div >
156+ ) }
157+ </ div >
158+
159+ { /* Modal Footer */ }
160+ < div className = { `pr-modal-footer ${ isDark ? "dark" : "light" } ` } >
161+ < a
162+ href = { contributor . profile }
163+ target = "_blank"
164+ rel = "noopener noreferrer"
165+ className = { `pr-modal-profile-link ${ isDark ? "dark" : "light" } ` }
166+ >
167+ < FaGithub />
168+ View GitHub Profile
169+ </ a >
170+ </ div >
171+ </ motion . div >
172+ </ motion . div >
173+ ) }
174+ </ AnimatePresence >
175+ ) ;
176+ }
0 commit comments