From 00d819f1e7210f2d2246094f4ff3501668a42a4a Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Wed, 1 Nov 2023 21:39:55 +0000 Subject: [PATCH 01/23] chore: add query for a user's groups --- site/src/api/queries/groups.ts | 49 +++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/site/src/api/queries/groups.ts b/site/src/api/queries/groups.ts index 3dc6759c12484..7a1bb02933bd1 100644 --- a/site/src/api/queries/groups.ts +++ b/site/src/api/queries/groups.ts @@ -8,6 +8,7 @@ import { } from "api/typesGenerated"; const GROUPS_QUERY_KEY = ["groups"]; +type GroupSortOrder = "asc" | "desc"; const getGroupQueryKey = (groupId: string) => ["group", groupId]; @@ -15,7 +16,7 @@ export const groups = (organizationId: string) => { return { queryKey: GROUPS_QUERY_KEY, queryFn: () => API.getGroups(organizationId), - }; + } satisfies UseQueryOptions; }; export const group = (groupId: string) => { @@ -33,18 +34,9 @@ export function groupsByUserId(organizationId: string) { select: (allGroups) => { // Sorting here means that nothing has to be sorted for the individual // user arrays later - const sorted = [...allGroups].sort((g1, g2) => { - const key = - g1.display_name && g2.display_name ? "display_name" : "name"; - - if (g1[key] === g2[key]) { - return 0; - } - - return g1[key] < g2[key] ? -1 : 1; - }); - + const sorted = sortGroupsByName(allGroups, "asc"); const userIdMapper = new Map(); + for (const group of sorted) { for (const user of group.members) { let groupsForUser = userIdMapper.get(user.id); @@ -62,6 +54,20 @@ export function groupsByUserId(organizationId: string) { } satisfies UseQueryOptions; } +export function groupsForUser(organizationId: string, userId: string) { + return { + ...groups(organizationId), + select: (allGroups) => { + const groupsForUser = allGroups.filter((group) => { + const groupMemberIds = group.members.map((member) => member.id); + return groupMemberIds.includes(userId); + }); + + return sortGroupsByName(groupsForUser, "asc"); + }, + } satisfies UseQueryOptions; +} + export const groupPermissions = (groupId: string) => { return { queryKey: [...getGroupQueryKey(groupId), "permissions"], @@ -136,3 +142,22 @@ export const invalidateGroup = (queryClient: QueryClient, groupId: string) => queryClient.invalidateQueries(GROUPS_QUERY_KEY), queryClient.invalidateQueries(getGroupQueryKey(groupId)), ]); + +export function sortGroupsByName( + groups: readonly Group[], + order: GroupSortOrder, +) { + return [...groups].sort((g1, g2) => { + const key = g1.display_name && g2.display_name ? "display_name" : "name"; + + if (g1[key] === g2[key]) { + return 0; + } + + if (order === "asc") { + return g1[key] < g2[key] ? -1 : 1; + } else { + return g1[key] < g2[key] ? 1 : -1; + } + }); +} From 1d646eba2b37fe09932e194c9c4c4feb07610e3c Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Wed, 1 Nov 2023 21:46:00 +0000 Subject: [PATCH 02/23] chore: integrate user groups into UI --- .../AccountPage/AccountPage.tsx | 86 +++++++++++++++---- 1 file changed, 71 insertions(+), 15 deletions(-) diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx index f266383aa2ca4..3fc920ed46da6 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx @@ -1,30 +1,86 @@ -import { FC } from "react"; +import { type FC } from "react"; import { Section } from "components/SettingsLayout/Section"; import { AccountForm } from "./AccountForm"; import { useAuth } from "components/AuthProvider/AuthProvider"; import { useMe } from "hooks/useMe"; import { usePermissions } from "hooks/usePermissions"; +import { Stack } from "@mui/system"; +import { useQuery } from "react-query"; +import { groupsForUser } from "api/queries/groups"; +import { useOrganizationId } from "hooks"; +import { useTheme } from "@emotion/react"; +import { Loader } from "components/Loader/Loader"; +import { Group } from "api/typesGenerated"; export const AccountPage: FC = () => { + const theme = useTheme(); const { updateProfile, updateProfileError, isUpdatingProfile } = useAuth(); - const me = useMe(); const permissions = usePermissions(); - const canEditUsers = permissions && permissions.updateUsers; + + const me = useMe(); + const organizationId = useOrganizationId(); + const groupsQuery = useQuery(groupsForUser(organizationId, me.id)); return ( -
- -
+ +
+ +
+ +
+ You are in{" "} + + {groupsQuery.data.length} groups + + + ) + } + > + {groupsQuery.isSuccess ? ( + + ) : ( + + )} +
+
); }; +type GroupListProps = { + groups: readonly Group[]; +}; + +function GroupList({ groups }: GroupListProps) { + const theme = useTheme(); + + return ( + <> + {groups.map((group) => ( +
+ {group.display_name || group.name} + {group.members.length} members +
+ ))} + + ); +} + export default AccountPage; From 024879de22b6ef61ebb24b12075d42bc7227f313 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Thu, 2 Nov 2023 12:17:13 +0000 Subject: [PATCH 03/23] refactor: split UI card into separate component --- site/src/api/queries/groups.ts | 2 +- site/src/components/AvatarCard/AvatarCard.tsx | 26 +++++++++++++++ .../AccountPage/AccountPage.tsx | 32 +++++++------------ 3 files changed, 38 insertions(+), 22 deletions(-) create mode 100644 site/src/components/AvatarCard/AvatarCard.tsx diff --git a/site/src/api/queries/groups.ts b/site/src/api/queries/groups.ts index 7a1bb02933bd1..6dff724772233 100644 --- a/site/src/api/queries/groups.ts +++ b/site/src/api/queries/groups.ts @@ -65,7 +65,7 @@ export function groupsForUser(organizationId: string, userId: string) { return sortGroupsByName(groupsForUser, "asc"); }, - } satisfies UseQueryOptions; + } as const satisfies UseQueryOptions; } export const groupPermissions = (groupId: string) => { diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/AvatarCard/AvatarCard.tsx new file mode 100644 index 0000000000000..43a6a30fdbe80 --- /dev/null +++ b/site/src/components/AvatarCard/AvatarCard.tsx @@ -0,0 +1,26 @@ +import { type ReactNode } from "react"; +import { useTheme } from "@emotion/react"; + +type AvatarCardProps = { + header: ReactNode; + imgUrl: string; + + subtitle?: string; + maxWidth?: number; +}; + +export function AvatarCard({ + header, + imgUrl, + subtitle, + maxWidth, +}: AvatarCardProps) { + const theme = useTheme(); + + return ( +
+ {header} + {subtitle && {subtitle}} +
+ ); +} diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx index 3fc920ed46da6..f6c9453b44dbd 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx @@ -9,8 +9,9 @@ import { useQuery } from "react-query"; import { groupsForUser } from "api/queries/groups"; import { useOrganizationId } from "hooks"; import { useTheme } from "@emotion/react"; + import { Loader } from "components/Loader/Loader"; -import { Group } from "api/typesGenerated"; +import { AvatarCard } from "components/AvatarCard/AvatarCard"; export const AccountPage: FC = () => { const theme = useTheme(); @@ -55,7 +56,15 @@ export const AccountPage: FC = () => { } > {groupsQuery.isSuccess ? ( - + <> + {groupsQuery.data.map((group) => ( + + ))} + ) : ( )} @@ -64,23 +73,4 @@ export const AccountPage: FC = () => { ); }; -type GroupListProps = { - groups: readonly Group[]; -}; - -function GroupList({ groups }: GroupListProps) { - const theme = useTheme(); - - return ( - <> - {groups.map((group) => ( -
- {group.display_name || group.name} - {group.members.length} members -
- ))} - - ); -} - export default AccountPage; From 102b1e01f6c0772772429ab06bb89f4d6a567612 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Thu, 2 Nov 2023 12:22:11 +0000 Subject: [PATCH 04/23] chore: enforce alt text for AvatarCard --- site/src/components/AvatarCard/AvatarCard.tsx | 18 +++++++++++++----- .../AccountPage/AccountPage.tsx | 19 ++++++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/AvatarCard/AvatarCard.tsx index 43a6a30fdbe80..c7d2d7e06e933 100644 --- a/site/src/components/AvatarCard/AvatarCard.tsx +++ b/site/src/components/AvatarCard/AvatarCard.tsx @@ -4,23 +4,31 @@ import { useTheme } from "@emotion/react"; type AvatarCardProps = { header: ReactNode; imgUrl: string; + altText: string; - subtitle?: string; - maxWidth?: number; + subtitle?: ReactNode; + width?: "sm" | "md" | "lg" | "full"; }; export function AvatarCard({ header, imgUrl, + altText, subtitle, - maxWidth, + width = "full", }: AvatarCardProps) { const theme = useTheme(); return (
- {header} - {subtitle && {subtitle}} +
+ {header} + {subtitle && {subtitle}} +
+ +
+ {altText} +
); } diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx index f6c9453b44dbd..99b4c5d951256 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx @@ -57,13 +57,18 @@ export const AccountPage: FC = () => { > {groupsQuery.isSuccess ? ( <> - {groupsQuery.data.map((group) => ( - - ))} + {groupsQuery.data.map((group) => { + const groupName = group.display_name || group.name; + + return ( + + ); + })} ) : ( From e8c713ccdcb2f43ce5559e55d4bd9edaedf6e5c2 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Thu, 2 Nov 2023 18:05:33 +0000 Subject: [PATCH 05/23] chore: add proper alt text support for Avatar --- site/src/components/Avatar/Avatar.tsx | 32 +++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/site/src/components/Avatar/Avatar.tsx b/site/src/components/Avatar/Avatar.tsx index 89de1f174ba4b..12108a053b919 100644 --- a/site/src/components/Avatar/Avatar.tsx +++ b/site/src/components/Avatar/Avatar.tsx @@ -1,8 +1,10 @@ // This is the only place MuiAvatar can be used // eslint-disable-next-line no-restricted-imports -- Read above import MuiAvatar, { AvatarProps as MuiAvatarProps } from "@mui/material/Avatar"; -import { FC } from "react"; +import { FC, useId } from "react"; import { css, type Interpolation, type Theme } from "@emotion/react"; +import { Box } from "@mui/system"; +import { visuallyHidden } from "@mui/utils"; export type AvatarProps = MuiAvatarProps & { size?: "xs" | "sm" | "md" | "xl"; @@ -66,18 +68,30 @@ export const Avatar: FC = ({ ); }; +type AvatarIconProps = { + src: string; + alt: string; +}; + /** * Use it to make an img element behaves like a MaterialUI Icon component */ -export const AvatarIcon: FC<{ src: string }> = ({ src }) => { +export const AvatarIcon: FC = ({ src, alt }) => { + const hookId = useId(); + const avatarId = `${hookId}-avatar`; + return ( - + <> + + + {alt} + + ); }; From 22e9573b6dd92dd1f9ced1956b13dc7047bece51 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Thu, 2 Nov 2023 18:15:39 +0000 Subject: [PATCH 06/23] fix: update props for Avatar call sites --- site/src/components/Avatar/Avatar.stories.tsx | 2 +- site/src/components/Resources/ResourceAvatar.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/components/Avatar/Avatar.stories.tsx b/site/src/components/Avatar/Avatar.stories.tsx index 67c5a5596fa54..fb510e6ee3e0f 100644 --- a/site/src/components/Avatar/Avatar.stories.tsx +++ b/site/src/components/Avatar/Avatar.stories.tsx @@ -65,7 +65,7 @@ export const MuiIconXL = { export const AvatarIconDarken = { args: { - children: , + children: , colorScheme: "darken", }, }; diff --git a/site/src/components/Resources/ResourceAvatar.tsx b/site/src/components/Resources/ResourceAvatar.tsx index 35aa96987518f..102f0c128dc8b 100644 --- a/site/src/components/Resources/ResourceAvatar.tsx +++ b/site/src/components/Resources/ResourceAvatar.tsx @@ -40,7 +40,7 @@ export const ResourceAvatar: FC = ({ resource }) => { return ( - + ); }; From acf25009db98d038780cfff6a1fdaa8a0bf7c66b Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Thu, 2 Nov 2023 19:03:04 +0000 Subject: [PATCH 07/23] finish AccountPage changes --- .../AccountPage/AccountPage.tsx | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx index 99b4c5d951256..6137d1cb44941 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx @@ -1,15 +1,17 @@ import { type FC } from "react"; -import { Section } from "components/SettingsLayout/Section"; -import { AccountForm } from "./AccountForm"; -import { useAuth } from "components/AuthProvider/AuthProvider"; import { useMe } from "hooks/useMe"; import { usePermissions } from "hooks/usePermissions"; -import { Stack } from "@mui/system"; import { useQuery } from "react-query"; import { groupsForUser } from "api/queries/groups"; import { useOrganizationId } from "hooks"; import { useTheme } from "@emotion/react"; +import { Stack } from "@mui/system"; +import Grid from "@mui/material/Grid"; + +import { AccountForm } from "./AccountForm"; +import { useAuth } from "components/AuthProvider/AuthProvider"; +import { Section } from "components/SettingsLayout/Section"; import { Loader } from "components/Loader/Loader"; import { AvatarCard } from "components/AvatarCard/AvatarCard"; @@ -56,20 +58,27 @@ export const AccountPage: FC = () => { } > {groupsQuery.isSuccess ? ( - <> - {groupsQuery.data.map((group) => { - const groupName = group.display_name || group.name; - - return ( + + {groupsQuery.data.map((group) => ( + + {group.members.length} member + {group.members.length !== 1 && "s"} + + } /> - ); - })} - + + ))} + ) : ( )} From f4b9a636ccf82632e471899cbd0e15a8ff668265 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Thu, 2 Nov 2023 19:03:19 +0000 Subject: [PATCH 08/23] wip: commit progress on AvatarCard --- site/src/components/AvatarCard/AvatarCard.tsx | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/AvatarCard/AvatarCard.tsx index c7d2d7e06e933..30b54d1cf991c 100644 --- a/site/src/components/AvatarCard/AvatarCard.tsx +++ b/site/src/components/AvatarCard/AvatarCard.tsx @@ -1,5 +1,13 @@ import { type ReactNode } from "react"; -import { useTheme } from "@emotion/react"; +import { Avatar, AvatarIcon } from "components/Avatar/Avatar"; +import { + type CSSObject, + type Interpolation, + type Theme, + useTheme, +} from "@emotion/react"; + +type Width = "sm" | "md" | "lg" | "full"; type AvatarCardProps = { header: ReactNode; @@ -7,9 +15,16 @@ type AvatarCardProps = { altText: string; subtitle?: ReactNode; - width?: "sm" | "md" | "lg" | "full"; + width?: Width; }; +const renderedWidths = { + sm: (theme) => theme.spacing(2), + md: (theme) => theme.spacing(3), + lg: (theme) => theme.spacing(6), + full: { width: "100%" }, +} as const satisfies Record>; + export function AvatarCard({ header, imgUrl, @@ -20,14 +35,40 @@ export function AvatarCard({ const theme = useTheme(); return ( -
-
- {header} - {subtitle && {subtitle}} +
+
+
{header}
+ + {subtitle && ( +
+ {subtitle} +
+ )}
- {altText} + + +
); From 426260d22266950e0afdc58c64a6e0888a4ef374 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 3 Nov 2023 19:16:14 +0000 Subject: [PATCH 09/23] fix: add better UI error handling --- .../AccountPage/AccountPage.tsx | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx index 6137d1cb44941..2e0f8aad47b6a 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx @@ -14,6 +14,7 @@ import { useAuth } from "components/AuthProvider/AuthProvider"; import { Section } from "components/SettingsLayout/Section"; import { Loader } from "components/Loader/Loader"; import { AvatarCard } from "components/AvatarCard/AvatarCard"; +import { ErrorAlert } from "components/Alert/ErrorAlert"; export const AccountPage: FC = () => { const theme = useTheme(); @@ -57,31 +58,26 @@ export const AccountPage: FC = () => { ) } > - {groupsQuery.isSuccess ? ( - - {groupsQuery.data.map((group) => ( - - - {group.members.length} member - {group.members.length !== 1 && "s"} - - } - /> - - ))} - - ) : ( - - )} + {groupsQuery.isLoading && } + {groupsQuery.isError && } + + + {groupsQuery.data?.map((group) => ( + + + {group.members.length} member + {group.members.length !== 1 && "s"} + + } + /> + + ))} + ); From fecf41e800e28729ba6105a32cfa7071b6094020 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 3 Nov 2023 19:28:06 +0000 Subject: [PATCH 10/23] fix: update theme setup for AvatarCard --- site/src/components/AvatarCard/AvatarCard.tsx | 46 ++++++------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/AvatarCard/AvatarCard.tsx index 30b54d1cf991c..1c53423e862f5 100644 --- a/site/src/components/AvatarCard/AvatarCard.tsx +++ b/site/src/components/AvatarCard/AvatarCard.tsx @@ -1,13 +1,6 @@ import { type ReactNode } from "react"; import { Avatar, AvatarIcon } from "components/Avatar/Avatar"; -import { - type CSSObject, - type Interpolation, - type Theme, - useTheme, -} from "@emotion/react"; - -type Width = "sm" | "md" | "lg" | "full"; +import { type CSSObject, useTheme } from "@emotion/react"; type AvatarCardProps = { header: ReactNode; @@ -15,40 +8,29 @@ type AvatarCardProps = { altText: string; subtitle?: ReactNode; - width?: Width; }; -const renderedWidths = { - sm: (theme) => theme.spacing(2), - md: (theme) => theme.spacing(3), - lg: (theme) => theme.spacing(6), - full: { width: "100%" }, -} as const satisfies Record>; - export function AvatarCard({ header, imgUrl, altText, subtitle, - width = "full", }: AvatarCardProps) { const theme = useTheme(); return ( -
{header}
@@ -70,6 +52,6 @@ export function AvatarCard({
-
+ ); } From 831d1103742bcb6afc72fabebf267c0636822070 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 3 Nov 2023 19:56:14 +0000 Subject: [PATCH 11/23] fix: update styling for AccountPage --- .../AccountPage/AccountPage.tsx | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx index 2e0f8aad47b6a..e44ca916f928d 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx @@ -58,26 +58,31 @@ export const AccountPage: FC = () => { ) } > - {groupsQuery.isLoading && } - {groupsQuery.isError && } +
+ {} - - {groupsQuery.data?.map((group) => ( - - - {group.members.length} member - {group.members.length !== 1 && "s"} - - } - /> - - ))} - + + {groupsQuery.data?.map((group) => ( + + + {group.members.length} member + {group.members.length !== 1 && "s"} + + } + /> + + ))} + + + {groupsQuery.isLoading && } +
); From 69fab822f97546625fee8ef87da36748ede254b6 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 3 Nov 2023 19:57:33 +0000 Subject: [PATCH 12/23] fix: make error message conditional --- site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx index e44ca916f928d..41d96d9fb0378 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx @@ -61,7 +61,7 @@ export const AccountPage: FC = () => {
- {} + {groupsQuery.isError && } {groupsQuery.data?.map((group) => ( From f7cffd10a62faa9594d0a3c9a03875ba50d4471c Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 3 Nov 2023 21:06:11 +0000 Subject: [PATCH 13/23] chore: update styling for AvatarCard --- site/src/components/AvatarCard/AvatarCard.tsx | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/AvatarCard/AvatarCard.tsx index 1c53423e862f5..0a829820e5586 100644 --- a/site/src/components/AvatarCard/AvatarCard.tsx +++ b/site/src/components/AvatarCard/AvatarCard.tsx @@ -1,9 +1,9 @@ import { type ReactNode } from "react"; -import { Avatar, AvatarIcon } from "components/Avatar/Avatar"; +import { Avatar } from "components/Avatar/Avatar"; import { type CSSObject, useTheme } from "@emotion/react"; type AvatarCardProps = { - header: ReactNode; + header: string; imgUrl: string; altText: string; @@ -32,8 +32,22 @@ export function AvatarCard({ cursor: "default", }} > -
-
{header}
+
+

+ {header} +

{subtitle && (
-
- - - -
+ + {header} + ); } From 2e43d5f9c0ef2e1feddaeca57a7807b9688745cd Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 3 Nov 2023 21:52:13 +0000 Subject: [PATCH 14/23] chore: finish AvatarCard --- site/src/components/AvatarCard/AvatarCard.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/AvatarCard/AvatarCard.tsx index 0a829820e5586..bd453f23d1a19 100644 --- a/site/src/components/AvatarCard/AvatarCard.tsx +++ b/site/src/components/AvatarCard/AvatarCard.tsx @@ -1,6 +1,7 @@ import { type ReactNode } from "react"; import { Avatar } from "components/Avatar/Avatar"; import { type CSSObject, useTheme } from "@emotion/react"; +import { colors } from "theme/colors"; type AvatarCardProps = { header: string; @@ -38,7 +39,7 @@ export function AvatarCard({ css={[ theme.typography.body1 as CSSObject, { - lineHeight: 1.5, + lineHeight: 1.4, margin: 0, overflow: "hidden", whiteSpace: "nowrap", @@ -61,7 +62,12 @@ export function AvatarCard({ )}
- + {header} From 7c322fa06c9c17c8d09f7eba518c675c6a5de488 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 3 Nov 2023 22:03:34 +0000 Subject: [PATCH 15/23] fix: add maxWidth support to AvatarCard --- site/src/components/AvatarCard/AvatarCard.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/AvatarCard/AvatarCard.tsx index bd453f23d1a19..e50aa0b5f277b 100644 --- a/site/src/components/AvatarCard/AvatarCard.tsx +++ b/site/src/components/AvatarCard/AvatarCard.tsx @@ -9,6 +9,7 @@ type AvatarCardProps = { altText: string; subtitle?: ReactNode; + maxWidth?: number | "none"; }; export function AvatarCard({ @@ -16,13 +17,14 @@ export function AvatarCard({ imgUrl, altText, subtitle, + maxWidth = 420, }: AvatarCardProps) { const theme = useTheme(); return (
Date: Fri, 3 Nov 2023 22:17:08 +0000 Subject: [PATCH 16/23] chore: update how no max width is defined --- site/src/components/AvatarCard/AvatarCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/AvatarCard/AvatarCard.tsx index e50aa0b5f277b..cdff502f08c02 100644 --- a/site/src/components/AvatarCard/AvatarCard.tsx +++ b/site/src/components/AvatarCard/AvatarCard.tsx @@ -17,14 +17,14 @@ export function AvatarCard({ imgUrl, altText, subtitle, - maxWidth = 420, + maxWidth = "none", }: AvatarCardProps) { const theme = useTheme(); return (
Date: Fri, 3 Nov 2023 22:35:44 +0000 Subject: [PATCH 17/23] chore: add AvatarCard stories --- .../AvatarCard/AvatarCard.stories.tsx | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 site/src/components/AvatarCard/AvatarCard.stories.tsx diff --git a/site/src/components/AvatarCard/AvatarCard.stories.tsx b/site/src/components/AvatarCard/AvatarCard.stories.tsx new file mode 100644 index 0000000000000..9f0204a9c844c --- /dev/null +++ b/site/src/components/AvatarCard/AvatarCard.stories.tsx @@ -0,0 +1,32 @@ +import { type Meta, type StoryObj } from "@storybook/react"; +import { AvatarCard } from "./AvatarCard"; + +const meta: Meta = { + title: "components/AvatarCard", + component: AvatarCard, +}; + +export default meta; +type Story = StoryObj; + +export const WithImage: Story = { + args: { + header: "Coder", + imgUrl: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4", + altText: "Coder", + subtitle: "56 members", + }, +}; + +export const WithoutImage: Story = { + args: { + header: "Patrick Star", + subtitle: "Friends with 723 people", + }, +}; + +export const WithoutSubtitleOrImage: Story = { + args: { + header: "Sandy Cheeks", + }, +}; From 43ae56b8ce21059849883bbc6cef897495b090ed Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Mon, 6 Nov 2023 13:50:52 +0000 Subject: [PATCH 18/23] fix: remove incorrect semantics for AvatarCard --- site/src/components/AvatarCard/AvatarCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/AvatarCard/AvatarCard.tsx index cdff502f08c02..81433b0fd32cf 100644 --- a/site/src/components/AvatarCard/AvatarCard.tsx +++ b/site/src/components/AvatarCard/AvatarCard.tsx @@ -22,7 +22,7 @@ export function AvatarCard({ const theme = useTheme(); return ( -
{header} -
+
); } From ba57fff3bcc027fa928ceb9e886e0fff132f88ec Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Mon, 6 Nov 2023 13:53:01 +0000 Subject: [PATCH 19/23] docs: add comment about flexbox behavior --- site/src/components/AvatarCard/AvatarCard.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/AvatarCard/AvatarCard.tsx index 81433b0fd32cf..ed991540fddd0 100644 --- a/site/src/components/AvatarCard/AvatarCard.tsx +++ b/site/src/components/AvatarCard/AvatarCard.tsx @@ -35,6 +35,12 @@ export function AvatarCard({ cursor: "default", }} > + {/** + * minWidth is necessary to ensure that the text truncation works properly + * with flex containers that don't have fixed width + * + * @see {@link https://css-tricks.com/flexbox-truncated-text/} + */}

Date: Mon, 6 Nov 2023 13:56:55 +0000 Subject: [PATCH 20/23] docs: add clarifying text about prop --- site/src/components/AvatarCard/AvatarCard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/AvatarCard/AvatarCard.tsx index ed991540fddd0..7896789abf5b1 100644 --- a/site/src/components/AvatarCard/AvatarCard.tsx +++ b/site/src/components/AvatarCard/AvatarCard.tsx @@ -43,6 +43,7 @@ export function AvatarCard({ */}

Date: Mon, 6 Nov 2023 14:00:20 +0000 Subject: [PATCH 21/23] fix: fix grammar for singular groups --- site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx index 41d96d9fb0378..9aad246519df8 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx @@ -52,7 +52,8 @@ export const AccountPage: FC = () => { fontWeight: 600, }} > - {groupsQuery.data.length} groups + {groupsQuery.data.length} group + {groupsQuery.data.length !== 1 && "s"} ) From 68ae492509038ceea2b97966b84f16482191cde4 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Mon, 6 Nov 2023 14:47:47 +0000 Subject: [PATCH 22/23] refactor: split off AccountUserGroups and add story --- .../AccountPage/AccountPage.tsx | 63 ++-------------- .../AccountPage/AccountUserGroups.stories.tsx | 64 ++++++++++++++++ .../AccountPage/AccountUserGroups.tsx | 73 +++++++++++++++++++ 3 files changed, 145 insertions(+), 55 deletions(-) create mode 100644 site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.stories.tsx create mode 100644 site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.tsx diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx index 9aad246519df8..4a38df0a1852e 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.tsx @@ -4,20 +4,14 @@ import { usePermissions } from "hooks/usePermissions"; import { useQuery } from "react-query"; import { groupsForUser } from "api/queries/groups"; import { useOrganizationId } from "hooks"; -import { useTheme } from "@emotion/react"; +import { useAuth } from "components/AuthProvider/AuthProvider"; import { Stack } from "@mui/system"; -import Grid from "@mui/material/Grid"; - +import { AccountUserGroups } from "./AccountUserGroups"; import { AccountForm } from "./AccountForm"; -import { useAuth } from "components/AuthProvider/AuthProvider"; import { Section } from "components/SettingsLayout/Section"; -import { Loader } from "components/Loader/Loader"; -import { AvatarCard } from "components/AvatarCard/AvatarCard"; -import { ErrorAlert } from "components/Alert/ErrorAlert"; export const AccountPage: FC = () => { - const theme = useTheme(); const { updateProfile, updateProfileError, isUpdatingProfile } = useAuth(); const permissions = usePermissions(); @@ -38,53 +32,12 @@ export const AccountPage: FC = () => { /> -
- You are in{" "} - - {groupsQuery.data.length} group - {groupsQuery.data.length !== 1 && "s"} - - - ) - } - > -
- {groupsQuery.isError && } - - - {groupsQuery.data?.map((group) => ( - - - {group.members.length} member - {group.members.length !== 1 && "s"} - - } - /> - - ))} - - - {groupsQuery.isLoading && } -
-
+ {/* Has
embedded inside because its description is dynamic */} + ); }; diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.stories.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.stories.tsx new file mode 100644 index 0000000000000..0f1f5d4192b03 --- /dev/null +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.stories.tsx @@ -0,0 +1,64 @@ +import { type Group } from "api/typesGenerated"; +import { type Meta, type StoryObj } from "@storybook/react"; + +import { AccountUserGroups } from "./AccountUserGroups"; +import { MockGroup as MockGroup1, mockApiError } from "testHelpers/entities"; + +const MockGroup2: Group = { + ...MockGroup1, + avatar_url: "", + display_name: "Goofy Goobers", +}; + +const mockError = mockApiError({ + message: "Failed to retrieve your groups", +}); + +const meta: Meta = { + title: "pages/UserSettingsPage/AccountUserGroups", + component: AccountUserGroups, + args: { + groups: [MockGroup1, MockGroup2], + loading: false, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Example: Story = {}; + +export const NoGroups: Story = { + args: { + groups: [], + }, +}; + +export const OneGroup: Story = { + args: { + groups: [MockGroup1], + }, +}; + +export const Loading: Story = { + args: { + groups: undefined, + loading: true, + }, +}; + +export const Error: Story = { + args: { + groups: undefined, + error: mockError, + loading: false, + }, +}; + +export const ErrorWithPreviousData: Story = { + args: { + groups: [MockGroup1, MockGroup2], + error: mockError, + loading: false, + }, +}; diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.tsx new file mode 100644 index 0000000000000..4a6a6dd969c04 --- /dev/null +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.tsx @@ -0,0 +1,73 @@ +import { useTheme } from "@emotion/react"; +import { isApiError } from "api/errors"; +import { type Group } from "api/typesGenerated"; + +import { ErrorAlert } from "components/Alert/ErrorAlert"; +import { AvatarCard } from "components/AvatarCard/AvatarCard"; +import { Loader } from "components/Loader/Loader"; +import { Section } from "components/SettingsLayout/Section"; +import Grid from "@mui/material/Grid"; + +type AccountGroupsProps = { + groups: readonly Group[] | undefined; + error: unknown; + loading: boolean; +}; + +export function AccountUserGroups({ + groups, + error, + loading, +}: AccountGroupsProps) { + const theme = useTheme(); + + return ( +
+ You are in{" "} + + {groups.length} group + {groups.length !== 1 && "s"} + + + ) + } + > +
+ {isApiError(error) && } + + {groups && ( + + {groups.map((group) => ( + + + {group.members.length} member + {group.members.length !== 1 && "s"} + + } + /> + + ))} + + )} + + {loading && } +
+
+ ); +} From d56a9ccf773c789326112d7eedbbfc6d99208846 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Mon, 6 Nov 2023 14:51:35 +0000 Subject: [PATCH 23/23] fix: differentiate mock groups more --- .../AccountPage/AccountUserGroups.stories.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.stories.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.stories.tsx index 0f1f5d4192b03..9ce7df88124e3 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.stories.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.stories.tsx @@ -2,12 +2,17 @@ import { type Group } from "api/typesGenerated"; import { type Meta, type StoryObj } from "@storybook/react"; import { AccountUserGroups } from "./AccountUserGroups"; -import { MockGroup as MockGroup1, mockApiError } from "testHelpers/entities"; +import { + MockGroup as MockGroup1, + MockUser, + mockApiError, +} from "testHelpers/entities"; const MockGroup2: Group = { ...MockGroup1, avatar_url: "", display_name: "Goofy Goobers", + members: [MockUser], }; const mockError = mockApiError({