Skip to content

Commit 331453c

Browse files
[dasboard] Avatar selection improvements (#20620)
* feat(dashboard): Enhance team onboarding with member avatar and framework selection - Add OrgMemberAvatarInput component with improved member selection using Popover and Command - Implement ComboboxDemo for framework selection - Update Button variant and add cmdk package - Improve UI components with more flexible selection and filtering Tool: gitpod/catfood.gitpod.cloud * fix(orgs): when updating org welcome msg settings, enforce updating `featuredMemberId` Tool: gitpod/catfood.gitpod.cloud * A proper fix with existing prebuild list combobox Tool: gitpod/catfood.gitpod.cloud * chore: Remove unused cmdk package and related components - Remove cmdk package from package.json - Delete Command.tsx component - Remove unused featuredMemberId state from TeamOnboarding - Clean up yarn.lock dependencies related to cmdk and radix-ui packages Tool: gitpod/catfood.gitpod.cloud * Clean up Tool: gitpod/catfood.gitpod.cloud
1 parent 0e2cafa commit 331453c

File tree

3 files changed

+105
-48
lines changed

3 files changed

+105
-48
lines changed

components/dashboard/src/components/podkit/buttons/Button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const buttonVariants = cva(
1717
default:
1818
"bg-gray-900 hover:bg-gray-800 dark:bg-kumquat-base dark:hover:bg-kumquat-ripe text-gray-50 dark:text-gray-900",
1919
destructive: "bg-red-600 hover:bg-red-700 text-gray-100 dark:text-red-100",
20-
outline: "border border-input bg-transparent hover:bg-kumquat-ripe hover:text-gray-600",
20+
outline: "border border-input bg-transparent hover:bg-pk-surface-secondary hover:text-gray-600",
2121
secondary:
2222
"bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-500 dark:text-gray-100 hover:text-gray-600",
2323
ghost: "bg-transparent hover:opacity-50",

components/dashboard/src/teams/onboarding/OrgMemberAvatarInput.tsx

Lines changed: 96 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,64 +4,113 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7-
import { useState } from "react";
7+
import { FC, useCallback, useMemo, useState } from "react";
88
import { useListOrganizationMembers } from "../../data/organizations/members-query";
9-
109
import type { OnboardingSettings_WelcomeMessage } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
11-
import { Button } from "@podkit/buttons/Button";
12-
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@podkit/dropdown/DropDown";
10+
import { Combobox } from "../../prebuilds/configuration-input/Combobox";
11+
import { ComboboxSelectedItem } from "../../prebuilds/configuration-input/ComboboxSelectedItem";
1312

1413
type Props = {
1514
settings: OnboardingSettings_WelcomeMessage | undefined;
1615
setFeaturedMemberId: (featuredMemberId: string | undefined) => void;
1716
};
1817
export const OrgMemberAvatarInput = ({ settings, setFeaturedMemberId }: Props) => {
19-
const { data: members } = useListOrganizationMembers();
20-
18+
const { data: members, isLoading } = useListOrganizationMembers();
19+
const [searchTerm, setSearchTerm] = useState("");
2120
const [avatarUrl, setAvatarUrl] = useState<string | undefined>(settings?.featuredMemberResolvedAvatarUrl);
2221

22+
const selectedMember = useMemo(() => {
23+
return members?.find((member) => member.avatarUrl === avatarUrl);
24+
}, [members, avatarUrl]);
25+
26+
const handleSelectionChange = useCallback(
27+
(selectedId: string) => {
28+
const member = members?.find((m) => m.userId === selectedId);
29+
setFeaturedMemberId(selectedId || undefined);
30+
setAvatarUrl(member?.avatarUrl);
31+
},
32+
[members, setFeaturedMemberId],
33+
);
34+
35+
const getElements = useCallback(() => {
36+
const resetFilterItem = {
37+
id: "",
38+
element: <SuggestedMemberOption member={{ fullName: "Disable image" }} />,
39+
isSelectable: true,
40+
};
41+
42+
if (!members) {
43+
return [resetFilterItem];
44+
}
45+
46+
const filteredMembers = members.filter((member) =>
47+
member.fullName.toLowerCase().includes(searchTerm.toLowerCase().trim()),
48+
);
49+
50+
const result = filteredMembers.map((member) => ({
51+
id: member.userId,
52+
element: <SuggestedMemberOption member={member} />,
53+
isSelectable: true,
54+
}));
55+
if (searchTerm.length === 0) result.unshift(resetFilterItem);
56+
57+
return result;
58+
}, [members, searchTerm]);
59+
2360
return (
24-
<DropdownMenu>
25-
<DropdownMenuTrigger>
26-
<div className="flex flex-col justify-center items-center gap-2">
27-
{avatarUrl ? (
28-
<img src={avatarUrl} alt="" className="w-16 h-16 rounded-full" />
29-
) : (
30-
<div className="w-16 h-16 rounded-full bg-[#EA71DE]" />
31-
)}
32-
<Button variant="secondary" size="sm" type="button">
33-
Change Photo
34-
</Button>
35-
</div>
36-
</DropdownMenuTrigger>
37-
<DropdownMenuContent>
38-
<DropdownMenuItem
39-
key="disabled"
40-
onClick={() => {
41-
setFeaturedMemberId(undefined);
42-
setAvatarUrl(undefined);
43-
}}
61+
<div className="flex flex-col items-center gap-4">
62+
{avatarUrl ? (
63+
<img src={avatarUrl} alt="" className="w-16 h-16 rounded-full" />
64+
) : (
65+
<div className="w-16 h-16 rounded-full bg-[#EA71DE]" />
66+
)}
67+
68+
<div className="w-48">
69+
<Combobox
70+
getElements={getElements}
71+
initialValue={selectedMember?.userId}
72+
onSelectionChange={handleSelectionChange}
73+
disabled={isLoading}
74+
loading={isLoading}
75+
onSearchChange={setSearchTerm}
76+
dropDownClassName="text-pk-content-primary"
4477
>
45-
<div className="flex items-center gap-2">
46-
<div className="w-4 h-4 rounded-full bg-pk-surface-tertiary" />
47-
Disable image
48-
</div>
49-
</DropdownMenuItem>
50-
{members?.map((member) => (
51-
<DropdownMenuItem
52-
key={member.userId}
53-
onClick={() => {
54-
setFeaturedMemberId(member.userId);
55-
setAvatarUrl(member.avatarUrl);
56-
}}
57-
>
58-
<div className="flex items-center gap-2">
59-
<img src={member.avatarUrl} alt={member.fullName} className="w-4 h-4 rounded-full" />
60-
{member.fullName}
61-
</div>
62-
</DropdownMenuItem>
63-
))}
64-
</DropdownMenuContent>
65-
</DropdownMenu>
78+
<ComboboxSelectedItem
79+
htmlTitle="Member"
80+
title={<div className="truncate">{selectedMember?.fullName ?? "Select member..."}</div>}
81+
titleClassName="text-sm font-normal text-pk-content-primary"
82+
loading={isLoading}
83+
icon={
84+
selectedMember?.avatarUrl ? (
85+
<img src={selectedMember.avatarUrl} alt="" className="w-4 h-4 rounded-full" />
86+
) : (
87+
<div className="w-4 h-4 rounded-full bg-pk-content-tertiary" />
88+
)
89+
}
90+
/>
91+
</Combobox>
92+
</div>
93+
</div>
94+
);
95+
};
96+
97+
type SuggestedMemberOptionProps = {
98+
member: {
99+
fullName: string;
100+
avatarUrl?: string;
101+
};
102+
};
103+
const SuggestedMemberOption: FC<SuggestedMemberOptionProps> = ({ member }) => {
104+
const { fullName, avatarUrl } = member;
105+
106+
return (
107+
<div className="flex flex-row items-center overflow-hidden" title={fullName} aria-label={`Member: ${fullName}`}>
108+
{avatarUrl ? (
109+
<img src={avatarUrl} alt="" className="w-4 h-4 rounded-full mr-2" />
110+
) : (
111+
<div className="w-4 h-4 rounded-full mr-2 bg-pk-content-tertiary" />
112+
)}
113+
{fullName && <span className="text-sm whitespace-nowrap">{fullName}</span>}
114+
</div>
66115
);
67116
};

components/server/src/orgs/organization-service.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,14 @@ export class OrganizationService {
629629
if (partialUpdate.roleRestrictions !== undefined) {
630630
settings.roleRestrictions = partialUpdate.roleRestrictions;
631631
}
632+
633+
if (partialUpdate.onboardingSettings?.welcomeMessage && settings.onboardingSettings?.welcomeMessage) {
634+
if (!partialUpdate.onboardingSettings.welcomeMessage.featuredMemberId) {
635+
settings.onboardingSettings.welcomeMessage.featuredMemberId = undefined;
636+
settings.onboardingSettings.welcomeMessage.featuredMemberResolvedAvatarUrl = undefined;
637+
}
638+
}
639+
632640
return settings;
633641
};
634642

0 commit comments

Comments
 (0)