Path: blob/main/components/dashboard/src/teams/onboarding/OrgMemberAvatarInput.tsx
2501 views
/**1* Copyright (c) 2025 Gitpod GmbH. All rights reserved.2* Licensed under the GNU Affero General Public License (AGPL).3* See License.AGPL.txt in the project root for license information.4*/56import { FC, useCallback, useMemo, useState } from "react";7import { useListOrganizationMembers } from "../../data/organizations/members-query";8import type { OnboardingSettings_WelcomeMessage } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";9import { Combobox } from "../../prebuilds/configuration-input/Combobox";10import { ComboboxSelectedItem } from "../../prebuilds/configuration-input/ComboboxSelectedItem";1112type Props = {13settings: OnboardingSettings_WelcomeMessage | undefined;14setFeaturedMemberId: (featuredMemberId: string | undefined) => void;15};16export const OrgMemberAvatarInput = ({ settings, setFeaturedMemberId }: Props) => {17const { data: members, isLoading } = useListOrganizationMembers();18const [searchTerm, setSearchTerm] = useState("");19const [avatarUrl, setAvatarUrl] = useState<string | undefined>(settings?.featuredMemberResolvedAvatarUrl);2021const selectedMember = useMemo(() => {22return members?.find((member) => member.avatarUrl === avatarUrl);23}, [members, avatarUrl]);2425const handleSelectionChange = useCallback(26(selectedId: string) => {27const member = members?.find((m) => m.userId === selectedId);28setFeaturedMemberId(selectedId);29setAvatarUrl(member?.avatarUrl);30},31[members, setFeaturedMemberId],32);3334const getElements = useCallback(() => {35const resetFilterItem = {36id: "",37element: <SuggestedMemberOption member={{ fullName: "Disable image" }} />,38isSelectable: true,39};4041if (!members) {42return [resetFilterItem];43}4445const filteredMembers = members.filter((member) =>46member.fullName.toLowerCase().includes(searchTerm.toLowerCase().trim()),47);4849const result = filteredMembers.map((member) => ({50id: member.userId,51element: <SuggestedMemberOption member={member} />,52isSelectable: true,53}));54if (searchTerm.length === 0) result.unshift(resetFilterItem);5556return result;57}, [members, searchTerm]);5859return (60<div className="flex flex-col items-center gap-4">61{avatarUrl ? (62<img src={avatarUrl} alt="" className="w-16 h-16 rounded-full" />63) : (64<div className="w-16 h-16 rounded-full bg-[#EA71DE]" />65)}6667<div className="w-48">68<Combobox69getElements={getElements}70initialValue={selectedMember?.userId}71onSelectionChange={handleSelectionChange}72disabled={isLoading}73loading={isLoading}74onSearchChange={setSearchTerm}75dropDownClassName="text-pk-content-primary"76>77<ComboboxSelectedItem78htmlTitle="Member"79title={<div className="truncate">{selectedMember?.fullName ?? "Select member..."}</div>}80titleClassName="text-sm font-normal text-pk-content-primary"81loading={isLoading}82icon={83selectedMember?.avatarUrl ? (84<img src={selectedMember.avatarUrl} alt="" className="w-4 h-4 rounded-full" />85) : (86<div className="w-4 h-4 rounded-full bg-pk-content-tertiary" />87)88}89/>90</Combobox>91</div>92</div>93);94};9596type SuggestedMemberOptionProps = {97member: {98fullName: string;99avatarUrl?: string;100};101};102const SuggestedMemberOption: FC<SuggestedMemberOptionProps> = ({ member }) => {103const { fullName, avatarUrl } = member;104105return (106<div className="flex flex-row items-center overflow-hidden" title={fullName} aria-label={`Member: ${fullName}`}>107{avatarUrl ? (108<img src={avatarUrl} alt="" className="w-4 h-4 rounded-full mr-2" />109) : (110<div className="w-4 h-4 rounded-full mr-2 bg-pk-content-tertiary" />111)}112{fullName && <span className="text-sm whitespace-nowrap">{fullName}</span>}113</div>114);115};116117118