diff --git a/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.stories.tsx b/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.stories.tsx index f5f5cb3e21963..4f04feab54b9f 100644 --- a/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.stories.tsx +++ b/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.stories.tsx @@ -60,3 +60,33 @@ export const Unauthenticated: Story = { }, }, }; + +export const Failed: Story = { + args: { + ...meta.args, + auths: { + providers: [MockGithubExternalProvider], + links: [ + { + ...MockGithubAuthLink, + validate_error: "Failed to refresh token", + }, + ], + }, + }, +}; + +export const NoRefresh: Story = { + args: { + ...meta.args, + auths: { + providers: [MockGithubExternalProvider], + links: [ + { + ...MockGithubAuthLink, + has_refresh_token: false, + }, + ], + }, + }, +}; diff --git a/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx b/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx index 4433fee43045b..b73286a6158f0 100644 --- a/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx +++ b/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx @@ -1,11 +1,16 @@ +import { useTheme } from "@emotion/react"; +import AutorenewIcon from "@mui/icons-material/Autorenew"; import LoadingButton from "@mui/lab/LoadingButton"; +import Badge from "@mui/material/Badge"; import Divider from "@mui/material/Divider"; +import { styled } from "@mui/material/styles"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableCell from "@mui/material/TableCell"; import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; +import Tooltip from "@mui/material/Tooltip"; import visuallyHidden from "@mui/utils/visuallyHidden"; import { type FC, useState, useCallback, useEffect } from "react"; import { useQuery } from "react-query"; @@ -104,6 +109,25 @@ interface ExternalAuthRowProps { onValidateExternalAuth: () => void; } +const StyledBadge = styled(Badge)(({ theme }) => ({ + "& .MuiBadge-badge": { + // Make a circular background for the icon. Background provides contrast, with a thin + // border to separate it from the avatar image. + backgroundColor: `${theme.palette.background.paper}`, + borderStyle: "solid", + borderColor: `${theme.palette.secondary.main}`, + borderWidth: "thin", + + // Override the default minimum sizes, as they are larger than what we want. + minHeight: "0px", + minWidth: "0px", + // Override the default "height", which is usually set to some constant value. + height: "auto", + // Padding adds some room for the icon to live in. + padding: "0.1em", + }, +})); + const ExternalAuthRow: FC = ({ app, unlinked, @@ -111,6 +135,7 @@ const ExternalAuthRow: FC = ({ onUnlinkExternalAuth, onValidateExternalAuth, }) => { + const theme = useTheme(); const name = app.display_name || app.id || app.type; const authURL = "/external-auth/" + app.id; @@ -125,22 +150,55 @@ const ExternalAuthRow: FC = ({ ? externalAuth.authenticated : link?.authenticated ?? false; + let avatar = app.display_icon ? ( + + ) : ( + {name} + ); + + // If the link is authenticated and has a refresh token, show that it will automatically + // attempt to authenticate when the token expires. + if (link?.has_refresh_token && authenticated) { + avatar = ( + + + + } + > + {avatar} + + ); + } + return ( - - ) - } - /> + + {link?.validate_error && ( + <> + + Error:{" "} + + {link?.validate_error} + + )}