forked from linode/manager
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: [M3-8158] - Begin to sunset Gravatar (linode#10859)
* Swap out gravatar for colored avatar on Profile > Display * Add utils to determine letter color * Bring back checkForGravatar event and fix hasGravatar boolean * Hide tooltip if user unless user hasGravatar * Add GravatarSunsetBanner.tsx * Use util for banner and profile page * Add Akamai wave icon for Akamai-generated user events * Clean up of theme colors and conditional rendering * Style ColorPicker * Try to handle constrast ratio * Address UX feedback: show avatar preview in dialog * Add new avatar to EventRow on Event Landing page * Conditionally render Gravatar in EventRow until sunset * Replace gravatar conditionally in UserRow of Users Landing * Conditionally render styled Avatar in UserSSHKeyPanel * Conditionally render Avatar in TopMenu; rename GravatarForProxy * Conditionally render styled Avatar in NotificationCenterEvent * Fix sunset date * Use MUI theme function to get contrasting text color * Clean up; change color default to darker color * Clean up Avatar, ColorPicker; add stories * Clean up and add unit tests * Clean up; fix test * Forgot to push last changes for Support; default color fix * Add changesets * Fix an accidentally skipped test * Address UX feedback: use 'Avatar' over 'Profile photo' * Address feedback: avoid regex * Fix bug: NotificationCenterEvent missing Linode system avatar * Use hook throughout gravatar replacement * improve useGravatar hook * Fix: showing new system/support avatars when user has Gravatar enabled * Experiment with component for loading/gravatar/avatar * Handle loading state and fade per-component to fix flickering * Fix username for support tickets; clean up * Fix avatar color for additional account users * Switch over to single GravatarOrAvatar component for rendering * Clean up commented code * Use const for default avatar size * Address feedback: use Map * Does not using the deprecated function fix the unit tests in CI? * Revert "Does not using the deprecated function fix the unit tests in CI?" This reverts commit c373a53. * Skip failing test with JSDom issues for now * Improve fading behavior * Revert "Improve fading behavior" because it causes other issues This reverts commit 04c0b6b. * Add GravatarByUsername loading placeholder; adjust other fades --------- Co-authored-by: Alban Bailly <abailly@akamai.com>
- Loading branch information
1 parent
8944441
commit 3bc6e76
Showing
30 changed files
with
758 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@linode/manager": Added | ||
--- | ||
|
||
Gravatar sunset banner for existing Gravatar users ([#10859](https://github.com/linode/manager/pull/10859)) |
5 changes: 5 additions & 0 deletions
5
packages/manager/.changeset/pr-10859-changed-1725550568902.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@linode/manager": Changed | ||
--- | ||
|
||
Avatars for users without Gravatars ([#10859](https://github.com/linode/manager/pull/10859)) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import * as React from 'react'; | ||
|
||
import { Avatar } from 'src/components/Avatar/Avatar'; | ||
|
||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import type { AvatarProps } from 'src/components/Avatar/Avatar'; | ||
|
||
export const Default: StoryObj<AvatarProps> = { | ||
render: (args) => <Avatar {...args} />, | ||
}; | ||
|
||
export const System: StoryObj<AvatarProps> = { | ||
render: (args) => <Avatar {...args} username="Linode" />, | ||
}; | ||
|
||
const meta: Meta<AvatarProps> = { | ||
args: { | ||
color: '#0174bc', | ||
height: 88, | ||
sx: {}, | ||
username: 'MyUsername', | ||
width: 88, | ||
}, | ||
component: Avatar, | ||
title: 'Components/Avatar', | ||
}; | ||
export default meta; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import * as React from 'react'; | ||
|
||
import { profileFactory } from 'src/factories/profile'; | ||
import { renderWithTheme } from 'src/utilities/testHelpers'; | ||
|
||
import { Avatar } from './Avatar'; | ||
|
||
import type { AvatarProps } from './Avatar'; | ||
|
||
const mockProps: AvatarProps = {}; | ||
|
||
const queryMocks = vi.hoisted(() => ({ | ||
useProfile: vi.fn().mockReturnValue({}), | ||
})); | ||
|
||
vi.mock('src/queries/profile/profile', async () => { | ||
const actual = await vi.importActual('src/queries/profile/profile'); | ||
return { | ||
...actual, | ||
useProfile: queryMocks.useProfile, | ||
}; | ||
}); | ||
|
||
describe('Avatar', () => { | ||
it('should render the first letter of a username from /profile with default background color', () => { | ||
queryMocks.useProfile.mockReturnValue({ | ||
data: profileFactory.build({ username: 'my-user' }), | ||
}); | ||
const { getByTestId } = renderWithTheme(<Avatar {...mockProps} />); | ||
const avatar = getByTestId('avatar'); | ||
const avatarStyles = getComputedStyle(avatar); | ||
|
||
expect(getByTestId('avatar-letter')).toHaveTextContent('M'); | ||
expect(avatarStyles.backgroundColor).toBe('rgb(1, 116, 188)'); // theme.color.primary.dark (#0174bc) | ||
}); | ||
|
||
it('should render a background color from props', () => { | ||
queryMocks.useProfile.mockReturnValue({ | ||
data: profileFactory.build({ username: 'my-user' }), | ||
}); | ||
|
||
const { getByTestId } = renderWithTheme( | ||
<Avatar {...mockProps} color="#000000" /> | ||
); | ||
const avatar = getByTestId('avatar'); | ||
const avatarText = getByTestId('avatar-letter'); | ||
const avatarStyles = getComputedStyle(avatar); | ||
const avatarTextStyles = getComputedStyle(avatarText); | ||
|
||
// Confirm background color contrasts with text color. | ||
expect(avatarStyles.backgroundColor).toBe('rgb(0, 0, 0)'); // black | ||
expect(avatarTextStyles.color).toBe('rgb(255, 255, 255)'); // white | ||
}); | ||
|
||
it('should render the first letter of username from props', async () => { | ||
const { getByTestId } = renderWithTheme( | ||
<Avatar {...mockProps} username="test" /> | ||
); | ||
|
||
expect(getByTestId('avatar-letter')).toHaveTextContent('T'); | ||
}); | ||
|
||
it('should render an svg instead of first letter for system users', async () => { | ||
const systemUsernames = ['Linode', 'lke-service-account-123']; | ||
|
||
systemUsernames.forEach((username, i) => { | ||
const { getAllByRole, queryByTestId } = renderWithTheme( | ||
<Avatar {...mockProps} username={username} /> | ||
); | ||
expect(getAllByRole('img')[i]).toBeVisible(); | ||
expect(queryByTestId('avatar-letter')).toBe(null); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { Typography, useTheme } from '@mui/material'; | ||
import { default as _Avatar } from '@mui/material/Avatar'; | ||
import * as React from 'react'; | ||
|
||
import AkamaiWave from 'src/assets/logo/akamai-wave.svg'; | ||
import { usePreferences } from 'src/queries/profile/preferences'; | ||
import { useProfile } from 'src/queries/profile/profile'; | ||
|
||
import type { SxProps } from '@mui/material'; | ||
|
||
export const DEFAULT_AVATAR_SIZE = 28; | ||
|
||
export interface AvatarProps { | ||
/** | ||
* Optional background color to override the color set in user preferences | ||
* */ | ||
color?: string; | ||
/** | ||
* Optional height | ||
* @default 28px | ||
* */ | ||
height?: number; | ||
/** | ||
* Optional styles | ||
* */ | ||
sx?: SxProps; | ||
/** | ||
* Optional username to override the profile username; will display the first letter | ||
* */ | ||
username?: string; | ||
/** | ||
* Optional width | ||
* @default 28px | ||
* */ | ||
width?: number; | ||
} | ||
|
||
/** | ||
* The Avatar component displays the first letter of a username on a solid background color. | ||
* For system avatars associated with Akamai-generated events, an Akamai logo is displayed in place of a letter. | ||
*/ | ||
export const Avatar = (props: AvatarProps) => { | ||
const { | ||
color, | ||
height = DEFAULT_AVATAR_SIZE, | ||
sx, | ||
username, | ||
width = DEFAULT_AVATAR_SIZE, | ||
} = props; | ||
|
||
const theme = useTheme(); | ||
|
||
const { data: preferences } = usePreferences(); | ||
const { data: profile } = useProfile(); | ||
|
||
const _username = username ?? profile?.username ?? ''; | ||
const isAkamai = | ||
_username === 'Linode' || _username.startsWith('lke-service-account'); | ||
|
||
const savedAvatarColor = | ||
isAkamai || !preferences?.avatarColor | ||
? theme.palette.primary.dark | ||
: preferences.avatarColor; | ||
const avatarLetter = _username[0]?.toUpperCase() ?? ''; | ||
|
||
return ( | ||
<_Avatar | ||
sx={{ | ||
'& svg': { | ||
height: width / 2, | ||
width: width / 2, | ||
}, | ||
bgcolor: color ?? savedAvatarColor, | ||
height, | ||
width, | ||
...sx, | ||
}} | ||
alt={`Avatar for user ${username ?? profile?.email ?? ''}`} | ||
data-testid="avatar" | ||
> | ||
{isAkamai ? ( | ||
<AkamaiWave /> | ||
) : ( | ||
<Typography | ||
sx={{ | ||
color: theme.palette.getContrastText(color ?? savedAvatarColor), | ||
fontSize: width / 2, | ||
}} | ||
data-testid="avatar-letter" | ||
> | ||
{avatarLetter} | ||
</Typography> | ||
)} | ||
</_Avatar> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
packages/manager/src/components/ColorPicker/ColorPicker.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import React from 'react'; | ||
|
||
import { ColorPicker } from 'src/components/ColorPicker/ColorPicker'; | ||
|
||
import type { ColorPickerProps } from './ColorPicker'; | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
const meta: Meta<ColorPickerProps> = { | ||
args: { | ||
defaultColor: '#0174bc', | ||
label: 'Label for color picker', | ||
onChange: () => undefined, | ||
}, | ||
component: ColorPicker, | ||
title: 'Components/ColorPicker', | ||
}; | ||
|
||
export const Default: StoryObj<ColorPickerProps> = { | ||
render: (args) => { | ||
return <ColorPicker {...args} />; | ||
}, | ||
}; | ||
|
||
export default meta; |
54 changes: 54 additions & 0 deletions
54
packages/manager/src/components/ColorPicker/ColorPicker.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { useTheme } from '@mui/material'; | ||
import React, { useState } from 'react'; | ||
|
||
import type { CSSProperties } from 'react'; | ||
|
||
export interface ColorPickerProps { | ||
/** | ||
* Optional color to specify as a default | ||
* */ | ||
defaultColor?: string; | ||
/** | ||
* Optional styles for the input element | ||
* */ | ||
inputStyles?: CSSProperties; | ||
/** | ||
* Visually hidden label to semantically describe the color picker for accessibility | ||
* */ | ||
label: string; | ||
/** | ||
* Function to update the color based on user selection | ||
* */ | ||
onChange: (color: string) => void; | ||
} | ||
|
||
/** | ||
* The ColorPicker component serves as a wrapper for the native HTML input color picker. | ||
*/ | ||
export const ColorPicker = (props: ColorPickerProps) => { | ||
const { defaultColor, inputStyles, label, onChange } = props; | ||
|
||
const theme = useTheme(); | ||
const [color, setColor] = useState<string>( | ||
defaultColor ?? theme.palette.primary.dark | ||
); | ||
|
||
return ( | ||
<> | ||
<label className="visually-hidden" htmlFor="color-picker"> | ||
{label} | ||
</label> | ||
<input | ||
onChange={(e) => { | ||
setColor(e.target.value); | ||
onChange(e.target.value); | ||
}} | ||
color={color} | ||
id="color-picker" | ||
style={inputStyles} | ||
type="color" | ||
value={color} | ||
/> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.