mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: Add profile pictures to OAuth users (#3855)
This supports GitHub and OIDC login for profile pictures!
This commit is contained in:
@@ -458,6 +458,7 @@ export interface User {
|
||||
readonly status: UserStatus
|
||||
readonly organization_ids: string[]
|
||||
readonly roles: Role[]
|
||||
readonly avatar_url: string
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
|
||||
@@ -51,6 +51,7 @@ describe("NavbarView", () => {
|
||||
const mockUser = {
|
||||
...MockUser,
|
||||
username: "bryan",
|
||||
avatar_url: "",
|
||||
}
|
||||
|
||||
// When
|
||||
|
||||
@@ -5,8 +5,17 @@ import { firstLetter } from "../../util/firstLetter"
|
||||
export interface UserAvatarProps {
|
||||
className?: string
|
||||
username: string
|
||||
avatarURL: string
|
||||
}
|
||||
|
||||
export const UserAvatar: FC<UserAvatarProps> = ({ username, className }) => {
|
||||
return <Avatar className={className}>{firstLetter(username)}</Avatar>
|
||||
export const UserAvatar: FC<UserAvatarProps> = ({ username, className, avatarURL }) => {
|
||||
return (
|
||||
<Avatar className={className}>
|
||||
{avatarURL ? (
|
||||
<img alt={`${username}'s Avatar`} src={avatarURL} width="100%" />
|
||||
) : (
|
||||
firstLetter(username)
|
||||
)}
|
||||
</Avatar>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export const AuditLogExample = Template.bind({})
|
||||
AuditLogExample.args = {
|
||||
Avatar: {
|
||||
username: MockUser.username,
|
||||
avatarURL: "",
|
||||
},
|
||||
caption: MockUserAgent.ip_address,
|
||||
primaryText: MockUser.email,
|
||||
@@ -25,6 +26,7 @@ export const AuditLogEmptyUserExample = Template.bind({})
|
||||
AuditLogEmptyUserExample.args = {
|
||||
Avatar: {
|
||||
username: MockUser.username,
|
||||
avatarURL: "",
|
||||
},
|
||||
caption: MockUserAgent.ip_address,
|
||||
primaryText: "Deleted User",
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Helpers {
|
||||
export const Props: UserCellProps = {
|
||||
Avatar: {
|
||||
username: MockUser.username,
|
||||
avatarURL: "",
|
||||
},
|
||||
caption: MockUserAgent.ip_address,
|
||||
primaryText: MockUser.username,
|
||||
|
||||
@@ -37,7 +37,7 @@ export const UserDropdown: React.FC<React.PropsWithChildren<UserDropdownProps>>
|
||||
>
|
||||
<div className={styles.inner}>
|
||||
<Badge overlap="circle">
|
||||
<UserAvatar username={user.username} />
|
||||
<UserAvatar username={user.username} avatarURL={user.avatar_url} />
|
||||
</Badge>
|
||||
{anchorEl ? <CloseDropdown /> : <OpenDropdown />}
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,11 @@ export const UserDropdownContent: FC<UserDropdownContentProps> = ({
|
||||
<div className={styles.userInfo}>
|
||||
<div className={styles.root}>
|
||||
<div className={styles.avatarContainer}>
|
||||
<UserAvatar className={styles.avatar} username={user.username} />
|
||||
<UserAvatar
|
||||
className={styles.avatar}
|
||||
username={user.username}
|
||||
avatarURL={user.avatar_url}
|
||||
/>
|
||||
</div>
|
||||
<Typography className={styles.userName}>{user.username}</Typography>
|
||||
<Typography className={styles.userEmail}>{user.email}</Typography>
|
||||
|
||||
@@ -72,7 +72,20 @@ export const UsersTableBody: FC<React.PropsWithChildren<UsersTableBodyProps>> =
|
||||
return (
|
||||
<TableRow key={user.id}>
|
||||
<TableCell>
|
||||
<AvatarData title={user.username} subtitle={user.email} highlightTitle />
|
||||
<AvatarData
|
||||
title={user.username}
|
||||
subtitle={user.email}
|
||||
highlightTitle
|
||||
avatar={
|
||||
user.avatar_url ? (
|
||||
<img
|
||||
className={styles.avatar}
|
||||
alt={`${user.username}'s Avatar`}
|
||||
src={user.avatar_url}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className={combineClasses([
|
||||
@@ -139,4 +152,9 @@ const useStyles = makeStyles((theme) => ({
|
||||
suspended: {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
avatar: {
|
||||
width: theme.spacing(4.5),
|
||||
height: theme.spacing(4.5),
|
||||
borderRadius: "100%",
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -37,6 +37,7 @@ describe("AccountPage", () => {
|
||||
status: "active",
|
||||
organization_ids: ["123"],
|
||||
roles: [],
|
||||
avatar_url: "",
|
||||
...data,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -70,6 +70,7 @@ export const MockUser: TypesGen.User = {
|
||||
status: "active",
|
||||
organization_ids: ["fc0774ce-cc9e-48d4-80ae-88f7a4d4a8b0"],
|
||||
roles: [MockOwnerRole],
|
||||
avatar_url: "https://github.com/coder.png",
|
||||
}
|
||||
|
||||
export const MockUserAdmin: TypesGen.User = {
|
||||
@@ -80,6 +81,7 @@ export const MockUserAdmin: TypesGen.User = {
|
||||
status: "active",
|
||||
organization_ids: ["fc0774ce-cc9e-48d4-80ae-88f7a4d4a8b0"],
|
||||
roles: [MockUserAdminRole],
|
||||
avatar_url: "",
|
||||
}
|
||||
|
||||
export const MockUser2: TypesGen.User = {
|
||||
@@ -90,6 +92,7 @@ export const MockUser2: TypesGen.User = {
|
||||
status: "active",
|
||||
organization_ids: ["fc0774ce-cc9e-48d4-80ae-88f7a4d4a8b0"],
|
||||
roles: [],
|
||||
avatar_url: "",
|
||||
}
|
||||
|
||||
export const SuspendedMockUser: TypesGen.User = {
|
||||
@@ -100,6 +103,7 @@ export const SuspendedMockUser: TypesGen.User = {
|
||||
status: "suspended",
|
||||
organization_ids: ["fc0774ce-cc9e-48d4-80ae-88f7a4d4a8b0"],
|
||||
roles: [],
|
||||
avatar_url: "",
|
||||
}
|
||||
|
||||
export const MockOrganization: TypesGen.Organization = {
|
||||
|
||||
Reference in New Issue
Block a user