Add Members while Transferring Roadmap (#4534)

* Add members while Transferring Roadmap

* Implement Responsive in Roadmaps page
pull/4546/head
Arik Chakma 1 year ago committed by GitHub
parent 0bf287f1d6
commit 2cae13c090
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/components/CustomRoadmap/RoadmapListPage.tsx
  2. 65
      src/components/ShareOptions/ShareOptionsModal.tsx
  3. 30
      src/components/ShareOptions/ShareTeamMemberList.tsx
  4. 22
      src/components/ShareOptions/TransferToTeamList.tsx

@ -86,13 +86,13 @@ export function RoadmapListPage() {
<CreateRoadmapModal onClose={() => setIsCreatingRoadmap(false)} /> <CreateRoadmapModal onClose={() => setIsCreatingRoadmap(false)} />
)} )}
<div className="mb-6 flex items-center justify-between"> <div className="mb-6 flex flex-col justify-between gap-2 sm:flex-row sm:items-center sm:gap-0">
<div className="flex items-center gap-2"> <div className="flex grow items-center gap-2">
{tabTypes.map((tab) => { {tabTypes.map((tab) => {
return ( return (
<button <button
key={tab.value} key={tab.value}
className={`relative flex items-center justify-center rounded-md border p-1 px-3 text-sm ${ className={`relative flex w-full items-center justify-center whitespace-nowrap rounded-md border p-1 px-3 text-sm sm:w-auto ${
activeTab === tab.value ? ' border-gray-400 bg-gray-200 ' : '' activeTab === tab.value ? ' border-gray-400 bg-gray-200 ' : ''
} w-full sm:w-auto`} } w-full sm:w-auto`}
onClick={() => setActiveTab(tab.value)} onClick={() => setActiveTab(tab.value)}

@ -1,4 +1,4 @@
import { type ReactNode, useCallback, useState } from 'react'; import { type ReactNode, useCallback, useState, useMemo } from 'react';
import { Globe2, Loader2, Lock } from 'lucide-react'; import { Globe2, Loader2, Lock } from 'lucide-react';
import { type ListFriendsResponse, ShareFriendList } from './ShareFriendList'; import { type ListFriendsResponse, ShareFriendList } from './ShareFriendList';
import { TransferToTeamList } from './TransferToTeamList'; import { TransferToTeamList } from './TransferToTeamList';
@ -49,7 +49,10 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
const [isSettingsUpdated, setIsSettingsUpdated] = useState(false); const [isSettingsUpdated, setIsSettingsUpdated] = useState(false);
const [friends, setFriends] = useState<ListFriendsResponse>([]); const [friends, setFriends] = useState<ListFriendsResponse>([]);
const [teams, setTeams] = useState<UserTeamItem[]>([]); const [teams, setTeams] = useState<UserTeamItem[]>([]);
const [members, setMembers] = useState<TeamMemberList[]>([]);
// Using global team members loading state to avoid glitchy UI when switching between teams
const [isTeamMembersLoading, setIsTeamMembersLoading] = useState(false);
const membersCache = useMemo(() => new Map<string, TeamMemberList[]>(), []);
const [visibility, setVisibility] = useState(defaultVisibility); const [visibility, setVisibility] = useState(defaultVisibility);
const [sharedTeamMemberIds, setSharedTeamMemberIds] = useState<string[]>( const [sharedTeamMemberIds, setSharedTeamMemberIds] = useState<string[]>(
@ -118,7 +121,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
}; };
const handleTransferToTeam = useCallback( const handleTransferToTeam = useCallback(
async (teamId: string) => { async (teamId: string, sharedTeamMemberIds: string[]) => {
if (!roadmapId) { if (!roadmapId) {
return; return;
} }
@ -128,6 +131,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
`${import.meta.env.PUBLIC_API_URL}/v1-transfer-roadmap/${roadmapId}`, `${import.meta.env.PUBLIC_API_URL}/v1-transfer-roadmap/${roadmapId}`,
{ {
teamId, teamId,
sharedTeamMemberIds,
} }
); );
@ -187,6 +191,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
defaultSharedFriendIds.length > 0 ? defaultSharedFriendIds : [] defaultSharedFriendIds.length > 0 ? defaultSharedFriendIds : []
); );
} else if (visibility === 'team' && teamId) { } else if (visibility === 'team' && teamId) {
setIsTeamMembersLoading(true);
setSharedTeamMemberIds( setSharedTeamMemberIds(
defaultSharedMemberIds?.length > 0 ? defaultSharedMemberIds : [] defaultSharedMemberIds?.length > 0 ? defaultSharedMemberIds : []
); );
@ -225,24 +230,50 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
setSharedFriendIds={setSharedFriendIds} setSharedFriendIds={setSharedFriendIds}
/> />
)} )}
{/* For Team Roadmap */}
{visibility === 'team' && teamId && (
<ShareTeamMemberList
teamId={teamId}
sharedTeamMemberIds={sharedTeamMemberIds}
setSharedTeamMemberIds={setSharedTeamMemberIds}
membersCache={membersCache}
isTeamMembersLoading={isTeamMembersLoading}
setIsTeamMembersLoading={setIsTeamMembersLoading}
/>
)}
{canTransferRoadmap && ( {canTransferRoadmap && (
<>
<TransferToTeamList <TransferToTeamList
teams={teams} teams={teams}
setTeams={setTeams} setTeams={setTeams}
selectedTeamId={selectedTeamId} selectedTeamId={selectedTeamId}
setSelectedTeamId={setSelectedTeamId} setSelectedTeamId={setSelectedTeamId}
isTeamMembersLoading={isTeamMembersLoading}
setIsTeamMembersLoading={setIsTeamMembersLoading}
onTeamChange={() => {
setIsTeamMembersLoading(true);
setSharedTeamMemberIds([]);
}}
/> />
)} {selectedTeamId && (
<>
{/* For Team Roadmap */} <hr className="-mx-4 my-4" />
{visibility === 'team' && teamId && ( <div className="mb-4">
<ShareTeamMemberList <ShareTeamMemberList
teamId={teamId} title="Select who can access this roadmap. You can change this later."
teamId={selectedTeamId!}
sharedTeamMemberIds={sharedTeamMemberIds} sharedTeamMemberIds={sharedTeamMemberIds}
setSharedTeamMemberIds={setSharedTeamMemberIds} setSharedTeamMemberIds={setSharedTeamMemberIds}
members={members} membersCache={membersCache}
setMembers={setMembers} isTeamMembersLoading={isTeamMembersLoading}
setIsTeamMembersLoading={setIsTeamMembersLoading}
/> />
</div>
</>
)}
</>
)} )}
</div> </div>
@ -255,17 +286,23 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
Close Close
</button> </button>
{canTransferRoadmap ? ( {canTransferRoadmap && (
<UpdateAction <UpdateAction
disabled={isUpdateDisabled || isLoading} disabled={
isUpdateDisabled || isLoading || sharedTeamMemberIds.length === 0
}
onClick={() => { onClick={() => {
handleTransferToTeam(selectedTeamId!).then(() => null); handleTransferToTeam(selectedTeamId!, sharedTeamMemberIds).then(
() => null
);
}} }}
> >
{isLoading && <Loader2 className="h-4 w-4 animate-spin" />} {isLoading && <Loader2 className="h-4 w-4 animate-spin" />}
Transfer Transfer
</UpdateAction> </UpdateAction>
) : ( )}
{!canTransferRoadmap && (
<UpdateAction <UpdateAction
disabled={isUpdateDisabled || isLoading} disabled={isUpdateDisabled || isLoading}
onClick={() => { onClick={() => {

@ -33,27 +33,31 @@ export interface TeamMemberList extends TeamMemberDocument {
type ShareTeamMemberListProps = { type ShareTeamMemberListProps = {
teamId: string; teamId: string;
setMembers: (members: TeamMemberList[]) => void; title?: string;
members: TeamMemberList[];
sharedTeamMemberIds: string[]; sharedTeamMemberIds: string[];
setSharedTeamMemberIds: (sharedTeamMemberIds: string[]) => void; setSharedTeamMemberIds: (sharedTeamMemberIds: string[]) => void;
membersCache: Map<string, TeamMemberList[]>;
isTeamMembersLoading: boolean;
setIsTeamMembersLoading: (isLoading: boolean) => void;
}; };
export function ShareTeamMemberList(props: ShareTeamMemberListProps) { export function ShareTeamMemberList(props: ShareTeamMemberListProps) {
const { const {
setMembers, teamId,
members, title = 'Select Members',
sharedTeamMemberIds, sharedTeamMemberIds,
setSharedTeamMemberIds, setSharedTeamMemberIds,
teamId,
membersCache,
isTeamMembersLoading: isLoading,
setIsTeamMembersLoading: setIsLoading,
} = props; } = props;
const toast = useToast(); const toast = useToast();
const [isLoading, setIsLoading] = useState(true);
async function loadTeamMembers() { async function loadTeamMembers() {
if (members?.length > 0) { if (membersCache.has(teamId)) {
return; return;
} }
@ -67,21 +71,21 @@ export function ShareTeamMemberList(props: ShareTeamMemberListProps) {
return; return;
} }
setMembers(response); membersCache.set(teamId, response);
} }
useEffect(() => { useEffect(() => {
loadTeamMembers().finally(() => { loadTeamMembers().finally(() => {
setIsLoading(false); setIsLoading(false);
}); });
}, []); }, [teamId]);
const loadingMembers = isLoading && ( const loadingMembers = isLoading && (
<ul className="mt-2 grid grid-cols-3 gap-2.5"> <ul className="mt-2 grid grid-cols-3 gap-2.5">
{[...Array(3)].map((_, idx) => ( {[...Array(3)].map((_, idx) => (
<li <li
key={idx} key={idx}
className="flex min-h-[62px] animate-pulse items-center gap-2 rounded-md border p-2" className="flex min-h-[66px] animate-pulse items-center gap-2 rounded-md border p-2"
> >
<div className="h-8 w-8 shrink-0 rounded-full bg-gray-200" /> <div className="h-8 w-8 shrink-0 rounded-full bg-gray-200" />
<div className="inline-grid w-full"> <div className="inline-grid w-full">
@ -93,11 +97,13 @@ export function ShareTeamMemberList(props: ShareTeamMemberListProps) {
</ul> </ul>
); );
const members = membersCache.get(teamId) || [];
return ( return (
<> <>
{(members.length > 0 || isLoading) && ( {(members.length > 0 || isLoading) && (
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<p className="text-sm">Select Members</p> <p className="text-sm">{title}</p>
<label className="flex items-center gap-2"> <label className="flex items-center gap-2">
<input <input

@ -11,10 +11,22 @@ type TransferToTeamListProps = {
selectedTeamId: string | null; selectedTeamId: string | null;
setSelectedTeamId: (teamId: string | null) => void; setSelectedTeamId: (teamId: string | null) => void;
isTeamMembersLoading: boolean;
setIsTeamMembersLoading: (isLoading: boolean) => void;
onTeamChange: (teamId: string | null) => void;
}; };
export function TransferToTeamList(props: TransferToTeamListProps) { export function TransferToTeamList(props: TransferToTeamListProps) {
const { teams, setTeams, selectedTeamId, setSelectedTeamId } = props; const {
teams,
setTeams,
selectedTeamId,
setSelectedTeamId,
isTeamMembersLoading,
setIsTeamMembersLoading,
onTeamChange,
} = props;
const toast = useToast(); const toast = useToast();
@ -73,11 +85,17 @@ export function TransferToTeamList(props: TransferToTeamListProps) {
<li key={team._id}> <li key={team._id}>
<button <button
className={cn( className={cn(
'relative flex w-full items-center gap-2.5 rounded-lg border p-2.5', 'relative flex w-full items-center gap-2.5 rounded-lg border p-2.5 disabled:cursor-not-allowed disabled:opacity-70',
isSelected && 'border-gray-500 bg-gray-100 text-black' isSelected && 'border-gray-500 bg-gray-100 text-black'
)} )}
disabled={isTeamMembersLoading}
onClick={() => { onClick={() => {
if (isSelected) {
setSelectedTeamId(null);
} else {
setSelectedTeamId(team._id); setSelectedTeamId(team._id);
}
onTeamChange(team._id);
}} }}
> >
<img <img

Loading…
Cancel
Save