1
+ import { useTheme } from "@emotion/react" ;
2
+ import AutorenewIcon from "@mui/icons-material/Autorenew" ;
1
3
import LoadingButton from "@mui/lab/LoadingButton" ;
4
+ import Badge from "@mui/material/Badge" ;
2
5
import Divider from "@mui/material/Divider" ;
6
+ import { styled } from "@mui/material/styles" ;
3
7
import Table from "@mui/material/Table" ;
4
8
import TableBody from "@mui/material/TableBody" ;
5
9
import TableCell from "@mui/material/TableCell" ;
6
10
import TableContainer from "@mui/material/TableContainer" ;
7
11
import TableHead from "@mui/material/TableHead" ;
8
12
import TableRow from "@mui/material/TableRow" ;
13
+ import Tooltip from "@mui/material/Tooltip" ;
9
14
import visuallyHidden from "@mui/utils/visuallyHidden" ;
10
15
import { type FC , useState , useCallback , useEffect } from "react" ;
11
16
import { useQuery } from "react-query" ;
@@ -104,13 +109,33 @@ interface ExternalAuthRowProps {
104
109
onValidateExternalAuth : ( ) => void ;
105
110
}
106
111
112
+ const StyledBadge = styled ( Badge ) ( ( { theme } ) => ( {
113
+ "& .MuiBadge-badge" : {
114
+ // Make a circular background for the icon. Background provides contrast, with a thin
115
+ // border to separate it from the avatar image.
116
+ backgroundColor : `${ theme . palette . background . paper } ` ,
117
+ borderStyle : "solid" ,
118
+ borderColor : `${ theme . palette . secondary . main } ` ,
119
+ borderWidth : "thin" ,
120
+
121
+ // Override the default minimum sizes, as they are larger than what we want.
122
+ minHeight : "0px" ,
123
+ minWidth : "0px" ,
124
+ // Override the default "height", which is usually set to some constant value.
125
+ height : "auto" ,
126
+ // Padding adds some room for the icon to live in.
127
+ padding : "0.1em" ,
128
+ } ,
129
+ } ) ) ;
130
+
107
131
const ExternalAuthRow : FC < ExternalAuthRowProps > = ( {
108
132
app,
109
133
unlinked,
110
134
link,
111
135
onUnlinkExternalAuth,
112
136
onValidateExternalAuth,
113
137
} ) => {
138
+ const theme = useTheme ( ) ;
114
139
const name = app . display_name || app . id || app . type ;
115
140
const authURL = "/external-auth/" + app . id ;
116
141
@@ -125,22 +150,55 @@ const ExternalAuthRow: FC<ExternalAuthRowProps> = ({
125
150
? externalAuth . authenticated
126
151
: link ?. authenticated ?? false ;
127
152
153
+ let avatar = app . display_icon ? (
154
+ < Avatar src = { app . display_icon } variant = "square" fitImage size = "sm" />
155
+ ) : (
156
+ < Avatar > { name } </ Avatar >
157
+ ) ;
158
+
159
+ // If the link is authenticated and has a refresh token, show that it will automatically
160
+ // attempt to authenticate when the token expires.
161
+ if ( link ?. has_refresh_token && authenticated ) {
162
+ avatar = (
163
+ < StyledBadge
164
+ anchorOrigin = { {
165
+ vertical : "bottom" ,
166
+ horizontal : "right" ,
167
+ } }
168
+ color = "default"
169
+ overlap = "circular"
170
+ badgeContent = {
171
+ < Tooltip
172
+ title = "Authentication token will automatically refresh when expired."
173
+ placement = "right"
174
+ >
175
+ < AutorenewIcon
176
+ sx = { {
177
+ fontSize : "1em" ,
178
+ } }
179
+ />
180
+ </ Tooltip >
181
+ }
182
+ >
183
+ { avatar }
184
+ </ StyledBadge >
185
+ ) ;
186
+ }
187
+
128
188
return (
129
189
< TableRow key = { app . id } >
130
190
< TableCell >
131
- < AvatarData
132
- title = { name }
133
- avatar = {
134
- app . display_icon && (
135
- < Avatar
136
- src = { app . display_icon }
137
- variant = "square"
138
- fitImage
139
- size = "sm"
140
- />
141
- )
142
- }
143
- />
191
+ < AvatarData title = { name } avatar = { avatar } />
192
+ { link ?. validate_error && (
193
+ < >
194
+ < span
195
+ css = { { paddingLeft : "1em" , color : theme . palette . error . light } }
196
+ >
197
+ Error:{ " " }
198
+ </ span >
199
+ { link ?. validate_error }
200
+ </ >
201
+ ) }
144
202
</ TableCell >
145
203
< TableCell css = { { textAlign : "right" } } >
146
204
< LoadingButton
0 commit comments