Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/dashboard/src/teams/onboarding/OrgMemberAvatarInput.tsx
2501 views
1
/**
2
* Copyright (c) 2025 Gitpod GmbH. All rights reserved.
3
* Licensed under the GNU Affero General Public License (AGPL).
4
* See License.AGPL.txt in the project root for license information.
5
*/
6
7
import { FC, useCallback, useMemo, useState } from "react";
8
import { useListOrganizationMembers } from "../../data/organizations/members-query";
9
import type { OnboardingSettings_WelcomeMessage } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
10
import { Combobox } from "../../prebuilds/configuration-input/Combobox";
11
import { ComboboxSelectedItem } from "../../prebuilds/configuration-input/ComboboxSelectedItem";
12
13
type Props = {
14
settings: OnboardingSettings_WelcomeMessage | undefined;
15
setFeaturedMemberId: (featuredMemberId: string | undefined) => void;
16
};
17
export const OrgMemberAvatarInput = ({ settings, setFeaturedMemberId }: Props) => {
18
const { data: members, isLoading } = useListOrganizationMembers();
19
const [searchTerm, setSearchTerm] = useState("");
20
const [avatarUrl, setAvatarUrl] = useState<string | undefined>(settings?.featuredMemberResolvedAvatarUrl);
21
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);
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
60
return (
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"
77
>
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>
115
);
116
};
117
118