@@ -9,7 +9,11 @@ import moment from "moment";
9
9
import { useContext , useEffect , useState } from "react" ;
10
10
import { useLocation } from "react-router" ;
11
11
import Header from "../components/Header" ;
12
+ import DropDown from "../components/DropDown" ;
13
+ import { ItemsList , Item , ItemField , ItemFieldContextMenu } from "../components/ItemsList" ;
14
+ import Modal from "../components/Modal" ;
12
15
import { getGitpodService } from "../service/service" ;
16
+ import copy from '../images/copy.svg' ;
13
17
import { TeamsContext , getCurrentTeam } from "./teams-context" ;
14
18
15
19
@@ -18,6 +22,7 @@ export default function() {
18
22
const location = useLocation ( ) ;
19
23
const team = getCurrentTeam ( location , teams ) ;
20
24
const [ members , setMembers ] = useState < TeamMemberInfo [ ] > ( [ ] ) ;
25
+ const [ showInviteModal , setShowInviteModal ] = useState < boolean > ( false ) ;
21
26
22
27
useEffect ( ( ) => {
23
28
if ( ! team ) {
@@ -29,29 +34,105 @@ export default function() {
29
34
} ) ( ) ;
30
35
} , [ team ] ) ;
31
36
37
+ const getInviteURL = ( ) => {
38
+ const link = new URL ( window . location . href ) ;
39
+ link . pathname = '/join-team' ;
40
+ link . search = '?teamId=' + team ?. id ;
41
+ return link . href ;
42
+ }
43
+
44
+ const [ copied , setCopied ] = useState < boolean > ( false ) ;
45
+ const copyToClipboard = ( text : string ) => {
46
+ const el = document . createElement ( "textarea" ) ;
47
+ el . value = text ;
48
+ document . body . appendChild ( el ) ;
49
+ el . select ( ) ;
50
+ try {
51
+ document . execCommand ( "copy" ) ;
52
+ } finally {
53
+ document . body . removeChild ( el ) ;
54
+ }
55
+ setCopied ( true ) ;
56
+ setTimeout ( ( ) => setCopied ( false ) , 2000 ) ;
57
+ } ;
58
+
32
59
return < >
33
60
< Header title = "Members" subtitle = "Manage team members." />
34
61
< div className = "lg:px-28 px-10" >
35
- < div className = "mt-2 grid grid-cols-3 px-6 py-2 font-semibold border-t border-b border-gray-200 dark:border-gray-800" >
36
- < p className = "pl-14" > Name</ p >
37
- < p > Joined</ p >
38
- < p > Role</ p >
39
- </ div >
40
- { members . map ( m => < div className = "mt-2 grid grid-cols-3 p-6 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl" >
41
- < div className = "flex items-center" >
42
- < div className = "w-14" > { m . avatarUrl && < img className = "rounded-full w-8 h-8" src = { m . avatarUrl || '' } alt = { m . fullName } /> } </ div >
43
- < div >
44
- < div className = "text-base text-gray-900 dark:text-gray-50 font-medium" > { m . fullName } </ div >
45
- < p > { m . primaryEmail } </ p >
62
+ < div className = "flex mt-8" >
63
+ < div className = "flex" >
64
+ < div className = "py-4" >
65
+ < svg xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 16 16" width = "16" height = "16" > < path fill = "#A8A29E" d = "M6 2a4 4 0 100 8 4 4 0 000-8zM0 6a6 6 0 1110.89 3.477l4.817 4.816a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 010 6z" /> </ svg >
46
66
</ div >
67
+ < input type = "search" placeholder = "Search Members" onChange = { ( ) => { /* TODO */ } } />
47
68
</ div >
48
- < div className = "flex items-center" >
49
- < div className = "text-gray-400" > { moment ( m . memberSince ) . fromNow ( ) } </ div >
50
- </ div >
51
- < div className = "flex items-center" >
52
- < div className = " text-gray-400" > Owner</ div >
69
+ < div className = "flex-1" />
70
+ < div className = "py-3 pl-3" >
71
+ < DropDown prefix = "Role: " contextMenuWidth = "w-32" activeEntry = { 'All' } entries = { [ {
72
+ title : 'All' ,
73
+ onClick : ( ) => { /* TODO */ }
74
+ } , {
75
+ title : 'Owner' ,
76
+ onClick : ( ) => { /* TODO */ }
77
+ } , {
78
+ title : 'Member' ,
79
+ onClick : ( ) => { /* TODO */ }
80
+ } ] } />
53
81
</ div >
54
- </ div > ) }
82
+ < button onClick = { ( ) => setShowInviteModal ( true ) } className = "ml-2" > Invite Members</ button >
83
+ </ div >
84
+ < ItemsList className = "mt-2" >
85
+ < Item header = { true } className = "grid grid-cols-3" >
86
+ < ItemField >
87
+ < span className = "pl-14" > Name</ span >
88
+ </ ItemField >
89
+ < ItemField >
90
+ < span > Joined</ span >
91
+ </ ItemField >
92
+ < ItemField className = "flex items-center" >
93
+ < span className = "flex-grow" > Role</ span >
94
+ < ItemFieldContextMenu />
95
+ </ ItemField >
96
+ </ Item >
97
+ { members . map ( m => < Item className = "grid grid-cols-3" >
98
+ < ItemField className = "flex items-center" >
99
+ < div className = "w-14" > { m . avatarUrl && < img className = "rounded-full w-8 h-8" src = { m . avatarUrl || '' } alt = { m . fullName } /> } </ div >
100
+ < div >
101
+ < div className = "text-base text-gray-900 dark:text-gray-50 font-medium" > { m . fullName } </ div >
102
+ < p > { m . primaryEmail } </ p >
103
+ </ div >
104
+ </ ItemField >
105
+ < ItemField >
106
+ < span className = "text-gray-400" > { moment ( m . memberSince ) . fromNow ( ) } </ span >
107
+ </ ItemField >
108
+ < ItemField className = "flex items-center" >
109
+ < span className = "text-gray-400 flex-grow capitalize" > { m . role } </ span >
110
+ < ItemFieldContextMenu menuEntries = { [
111
+ {
112
+ title : 'Remove' ,
113
+ customFontStyle : 'text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300' ,
114
+ onClick : ( ) => { /* TODO(janx) */ }
115
+ } ,
116
+ ] } />
117
+ </ ItemField >
118
+ </ Item > ) }
119
+ </ ItemsList >
55
120
</ div >
121
+ { showInviteModal && < Modal visible = { true } onClose = { ( ) => setShowInviteModal ( false ) } >
122
+ < h3 className = "mb-4" > Invite Members</ h3 >
123
+ < div className = "border-t border-b border-gray-200 dark:border-gray-800 -mx-6 px-6 py-4 flex flex-col" >
124
+ < label htmlFor = "inviteUrl" className = "font-medium" > Invite URL</ label >
125
+ < div className = "w-full relative" >
126
+ < input name = "inviteUrl" disabled = { true } readOnly = { true } type = "text" value = { getInviteURL ( ) } className = "rounded-md w-full truncate pr-8" />
127
+ < div className = "cursor-pointer" onClick = { ( ) => copyToClipboard ( getInviteURL ( ) ) } >
128
+ < img src = { copy } title = "Copy Invite URL" className = "absolute top-1/3 right-3" />
129
+ </ div >
130
+ </ div >
131
+ < p className = "mt-1 text-gray-500 text-sm" > { copied ? 'Copied to clipboard!' : 'Use this URL to join this team as a Member.' } </ p >
132
+ </ div >
133
+ < div className = "flex justify-end mt-6" >
134
+ < button className = "secondary" onClick = { ( ) => setShowInviteModal ( false ) } > Done</ button >
135
+ </ div >
136
+ </ Modal > }
56
137
</ > ;
57
138
}
0 commit comments