Skip to content

Commit 35b8283

Browse files
jankeromnesroboquat
authored andcommitted
[dashboard] Improve team selection UX in new Project flow
1 parent 0df8824 commit 35b8283

File tree

1 file changed

+58
-45
lines changed

1 file changed

+58
-45
lines changed

components/dashboard/src/projects/NewProject.tsx

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { useContext, useEffect, useState } from "react";
88
import { getGitpodService, gitpodHostUrl } from "../service/service";
99
import { iconForAuthProvider, openAuthorizeWindow, simplifyProviderName } from "../provider-utils";
10-
import { AuthProviderInfo, ProviderRepository, Team, User } from "@gitpod/gitpod-protocol";
10+
import { AuthProviderInfo, ProviderRepository, Team, TeamMemberInfo, User } from "@gitpod/gitpod-protocol";
1111
import { TeamsContext } from "../teams/teams-context";
1212
import { useHistory, useLocation } from "react-router";
1313
import ContextMenu, { ContextMenuEntry } from "../components/ContextMenu";
@@ -65,6 +65,24 @@ export default function NewProject() {
6565
}
6666
}, []);
6767

68+
const [ teamMembers, setTeamMembers ] = useState<Record<string, TeamMemberInfo[]>>({});
69+
useEffect(() => {
70+
if (!teams) {
71+
return;
72+
}
73+
(async () => {
74+
const members: Record<string, TeamMemberInfo[]> = {};
75+
await Promise.all(teams.map(async (team) => {
76+
try {
77+
members[team.id] = await getGitpodService().server.getTeamMembers(team.id);
78+
} catch (error) {
79+
console.error('Could not get members of team', team, error);
80+
}
81+
}));
82+
setTeamMembers(members);
83+
})();
84+
}, [teams]);
85+
6886
useEffect(() => {
6987
if (selectedTeamOrUser && selectedRepo) {
7088
createProject(selectedTeamOrUser, selectedRepo);
@@ -91,7 +109,7 @@ export default function NewProject() {
91109
}, [selectedAccount]);
92110

93111
useEffect(() => {
94-
if (!provider || isBitbucket()) {
112+
if (!provider || isBitbucket()) {
95113
return;
96114
}
97115
(async () => {
@@ -101,7 +119,7 @@ export default function NewProject() {
101119
}, [provider]);
102120

103121
const isGitHub = () => provider === "github.com";
104-
const isBitbucket = () => provider == "bitbucket.org";
122+
const isBitbucket = () => provider === "bitbucket.org";
105123

106124
const updateReposInAccounts = async (installationId?: string) => {
107125
setLoaded(false);
@@ -163,7 +181,7 @@ export default function NewProject() {
163181
}
164182

165183
const createProject = async (teamOrUser: Team | User, selectedRepo: string) => {
166-
if (!provider || isBitbucket()) {
184+
if (!provider || isBitbucket()) {
167185
return;
168186
}
169187
const repo = reposInAccounts.find(r => r.account === selectedAccount && (r.path ? r.path === selectedRepo : r.name === selectedRepo));
@@ -243,6 +261,7 @@ export default function NewProject() {
243261
const showSearchInput = !!repoSearchFilter || filteredRepos.length > 0;
244262

245263
const renderRepos = () => (<>
264+
{!isBitbucket() && <p className="text-gray-500 text-center text-base">Select a Git repository on <strong>{provider}</strong>. (<a className="gp-link cursor-pointer" onClick={() => setShowGitProviders(true)}>change</a>)</p>}
246265
<div className={`mt-10 border rounded-xl border-gray-100 dark:border-gray-800 flex-col`}>
247266
<div className="px-8 pt-8 flex flex-col space-y-2" data-analytics='{"label":"Identity"}'>
248267
<ContextMenu classes="w-full left-0 cursor-pointer" menuEntries={getDropDownEntries(accounts)}>
@@ -357,39 +376,42 @@ export default function NewProject() {
357376
const userFullName = user?.fullName || user?.name || '...';
358377
const teamsToRender = teams || [];
359378
return (<>
360-
<h3 className="pb-2 mt-8">Select Team</h3>
361-
<h4 className="pb-2">Adding <strong>{selectedRepo}</strong></h4>
362-
363-
<div className="mt-8 border rounded-xl border-gray-100 dark:border-gray-800 flex-col" >
364-
<div key={`user-${userFullName}`} className={`w-96 border-b px-8 py-4 flex space-x-2 justify-between dark:hover:bg-gray-800 focus:bg-gitpod-kumquat-light transition ease-in-out group dark:border-gray-800 rounded-t-xl`}>
365-
<div className="w-8/12 m-auto overflow-ellipsis truncate">{userFullName}</div>
366-
<div className="w-4/12 flex justify-end">
367-
<div className="flex self-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md cursor-pointer opacity-0 group-hover:opacity-100">
368-
<button className="primary py-1" onClick={() => setSelectedTeamOrUser(user)}>Select</button>
369-
</div>
379+
<p className="mt-2 text-gray-500 text-center text-base">Select team or personal account</p>
380+
<div className="mt-14 flex flex-col space-y-2">
381+
<label key={`user-${userFullName}`} className={`w-80 px-4 py-3 flex space-x-3 items-center cursor-pointer rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800`} onClick={() => setSelectedTeamOrUser(user)}>
382+
<input type="radio" />
383+
<div className="flex-grow overflow-ellipsis truncate flex flex-col">
384+
<span className="font-semibold">{userFullName}</span>
385+
<span className="text-sm text-gray-400">Personal account</span>
370386
</div>
371-
</div>
387+
</label>
372388
{teamsToRender.map((t) => (
373-
<div key={`team-${t.name}`} className={`w-96 border-b px-8 py-4 flex space-x-2 justify-between dark:hover:bg-gray-800 focus:bg-gitpod-kumquat-light transition ease-in-out group dark:border-gray-800`}>
374-
<div className="w-8/12 m-auto overflow-ellipsis truncate">{t.name}</div>
375-
<div className="w-4/12 flex justify-end">
376-
<div className="flex self-center hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md cursor-pointer opacity-0 group-hover:opacity-100">
377-
<button className="primary py-1" onClick={() => setSelectedTeamOrUser(t)}>Select</button>
378-
</div>
389+
<label key={`team-${t.name}`} className={`w-80 px-4 py-3 flex space-x-3 items-center cursor-pointer rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800`} onClick={() => setSelectedTeamOrUser(t)}>
390+
<input type="radio" />
391+
<div className="flex-grow overflow-ellipsis truncate flex flex-col">
392+
<span className="font-semibold">{t.name}</span>
393+
<span className="text-sm text-gray-400">{!!teamMembers[t.id]
394+
? `${teamMembers[t.id].length} member${teamMembers[t.id].length === 1 ? '' : 's'}`
395+
: 'Team'
396+
}</span>
379397
</div>
380-
</div>
398+
</label>
381399
))}
382-
<div className="w-96 py-4 px-8 flex text-gray-500">
383-
<div className="w-full relative" onClick={() => setShowNewTeam(!showNewTeam)}>
384-
<div className="space-x-2">New Team</div>
400+
<label className="w-80 px-4 py-3 flex flex-col cursor-pointer rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800">
401+
<div className="flex space-x-3 items-center relative">
402+
<input type="radio" onChange={() => setShowNewTeam(!showNewTeam)} />
403+
<div className="flex-grow overflow-ellipsis truncate flex flex-col">
404+
<span className="font-semibold">Create new team</span>
405+
<span className="text-sm text-gray-400">Collaborate with others</span>
406+
</div>
385407
{teamsToRender.length > 0 && (
386-
<img src={CaretDown} title="Select Account" className={`${showNewTeam ? "transform rotate-180" : ""} filter-grayscale absolute top-1/2 right-3 cursor-pointer`} />
408+
<img alt="" src={CaretDown} title="Select Account" className={`${showNewTeam ? "transform rotate-180" : ""} filter-grayscale absolute top-1/2 right-3 cursor-pointer`} />
387409
)}
388410
</div>
389-
</div>
390-
{(showNewTeam || teamsToRender.length === 0) && (
391-
<NewTeam className="w-96 px-8 pb-8" onSuccess={(t) => setSelectedTeamOrUser(t)} />
392-
)}
411+
{(showNewTeam || teamsToRender.length === 0) && (
412+
<NewTeam onSuccess={(t) => setSelectedTeamOrUser(t)} />
413+
)}
414+
</label>
393415
</div>
394416
</>)
395417
};
@@ -406,15 +428,9 @@ export default function NewProject() {
406428
</div>);
407429
}
408430

409-
const renderSelectRepoHeading = () => {
410-
return <p className="text-gray-500 text-center text-base">Select a Git repository on <strong>{provider}</strong>. (<a className="gp-link cursor-pointer" onClick={() => setShowGitProviders(true)}>change</a>)</p>
411-
}
412-
413431
return (<div className="flex flex-col w-96 mt-24 mx-auto items-center">
414432
<h1>New Project</h1>
415433

416-
{isBitbucket() || renderSelectRepoHeading()}
417-
418434
{!selectedRepo && renderSelectRepository()}
419435

420436
{selectedRepo && !selectedTeamOrUser && renderSelectTeam()}
@@ -504,7 +520,6 @@ function GitProviders(props: {
504520

505521
function NewTeam(props: {
506522
onSuccess: (team: Team) => void,
507-
className?: string,
508523
}) {
509524
const { setTeams } = useContext(TeamsContext);
510525

@@ -530,15 +545,13 @@ function NewTeam(props: {
530545
setError(undefined);
531546
}
532547

533-
return (
534-
<div className={props.className}>
535-
<div className="flex flex-row space-x-2">
536-
<input type="text" className="py-1 flex-grow w-36" name="new-team-inline" value={teamName} placeholder="team-name" onChange={(e) => onTeamNameChanged(e.target.value)} />
537-
<button key={`new-team-inline-create`} disabled={!teamName} onClick={() => onNewTeam()}>Create Team</button>
538-
</div>
539-
{error && <p className="text-gitpod-red">{error}</p>}
548+
return <>
549+
<div className="mt-6 mb-1 flex flex-row space-x-2">
550+
<input type="text" className="py-1 min-w-0" name="new-team-inline" value={teamName} onChange={(e) => onTeamNameChanged(e.target.value)} />
551+
<button key={`new-team-inline-create`} disabled={!teamName} onClick={() => onNewTeam()}>Continue</button>
540552
</div>
541-
)
553+
{error && <p className="text-gitpod-red">{error}</p>}
554+
</>;
542555
}
543556

544557
async function openReconfigureWindow(params: { account?: string, onSuccess: (p: any) => void }) {

0 commit comments

Comments
 (0)