mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-01 06:50:26 +02:00
Add Members while Transferring Roadmap (#4534)
* Add members while Transferring Roadmap * Implement Responsive in Roadmaps page
This commit is contained in:
@@ -86,13 +86,13 @@ export function RoadmapListPage() {
|
||||
<CreateRoadmapModal onClose={() => setIsCreatingRoadmap(false)} />
|
||||
)}
|
||||
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="mb-6 flex flex-col justify-between gap-2 sm:flex-row sm:items-center sm:gap-0">
|
||||
<div className="flex grow items-center gap-2">
|
||||
{tabTypes.map((tab) => {
|
||||
return (
|
||||
<button
|
||||
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 ' : ''
|
||||
} w-full sm:w-auto`}
|
||||
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 { type ListFriendsResponse, ShareFriendList } from './ShareFriendList';
|
||||
import { TransferToTeamList } from './TransferToTeamList';
|
||||
@@ -49,7 +49,10 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
|
||||
const [isSettingsUpdated, setIsSettingsUpdated] = useState(false);
|
||||
const [friends, setFriends] = useState<ListFriendsResponse>([]);
|
||||
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 [sharedTeamMemberIds, setSharedTeamMemberIds] = useState<string[]>(
|
||||
@@ -118,7 +121,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
|
||||
};
|
||||
|
||||
const handleTransferToTeam = useCallback(
|
||||
async (teamId: string) => {
|
||||
async (teamId: string, sharedTeamMemberIds: string[]) => {
|
||||
if (!roadmapId) {
|
||||
return;
|
||||
}
|
||||
@@ -128,6 +131,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-transfer-roadmap/${roadmapId}`,
|
||||
{
|
||||
teamId,
|
||||
sharedTeamMemberIds,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -187,6 +191,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
|
||||
defaultSharedFriendIds.length > 0 ? defaultSharedFriendIds : []
|
||||
);
|
||||
} else if (visibility === 'team' && teamId) {
|
||||
setIsTeamMembersLoading(true);
|
||||
setSharedTeamMemberIds(
|
||||
defaultSharedMemberIds?.length > 0 ? defaultSharedMemberIds : []
|
||||
);
|
||||
@@ -225,14 +230,6 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
|
||||
setSharedFriendIds={setSharedFriendIds}
|
||||
/>
|
||||
)}
|
||||
{canTransferRoadmap && (
|
||||
<TransferToTeamList
|
||||
teams={teams}
|
||||
setTeams={setTeams}
|
||||
selectedTeamId={selectedTeamId}
|
||||
setSelectedTeamId={setSelectedTeamId}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* For Team Roadmap */}
|
||||
{visibility === 'team' && teamId && (
|
||||
@@ -240,10 +237,44 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
|
||||
teamId={teamId}
|
||||
sharedTeamMemberIds={sharedTeamMemberIds}
|
||||
setSharedTeamMemberIds={setSharedTeamMemberIds}
|
||||
members={members}
|
||||
setMembers={setMembers}
|
||||
membersCache={membersCache}
|
||||
isTeamMembersLoading={isTeamMembersLoading}
|
||||
setIsTeamMembersLoading={setIsTeamMembersLoading}
|
||||
/>
|
||||
)}
|
||||
|
||||
{canTransferRoadmap && (
|
||||
<>
|
||||
<TransferToTeamList
|
||||
teams={teams}
|
||||
setTeams={setTeams}
|
||||
selectedTeamId={selectedTeamId}
|
||||
setSelectedTeamId={setSelectedTeamId}
|
||||
isTeamMembersLoading={isTeamMembersLoading}
|
||||
setIsTeamMembersLoading={setIsTeamMembersLoading}
|
||||
onTeamChange={() => {
|
||||
setIsTeamMembersLoading(true);
|
||||
setSharedTeamMemberIds([]);
|
||||
}}
|
||||
/>
|
||||
{selectedTeamId && (
|
||||
<>
|
||||
<hr className="-mx-4 my-4" />
|
||||
<div className="mb-4">
|
||||
<ShareTeamMemberList
|
||||
title="Select who can access this roadmap. You can change this later."
|
||||
teamId={selectedTeamId!}
|
||||
sharedTeamMemberIds={sharedTeamMemberIds}
|
||||
setSharedTeamMemberIds={setSharedTeamMemberIds}
|
||||
membersCache={membersCache}
|
||||
isTeamMembersLoading={isTeamMembersLoading}
|
||||
setIsTeamMembersLoading={setIsTeamMembersLoading}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex items-center justify-between gap-1.5">
|
||||
@@ -255,17 +286,23 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
|
||||
Close
|
||||
</button>
|
||||
|
||||
{canTransferRoadmap ? (
|
||||
{canTransferRoadmap && (
|
||||
<UpdateAction
|
||||
disabled={isUpdateDisabled || isLoading}
|
||||
disabled={
|
||||
isUpdateDisabled || isLoading || sharedTeamMemberIds.length === 0
|
||||
}
|
||||
onClick={() => {
|
||||
handleTransferToTeam(selectedTeamId!).then(() => null);
|
||||
handleTransferToTeam(selectedTeamId!, sharedTeamMemberIds).then(
|
||||
() => null
|
||||
);
|
||||
}}
|
||||
>
|
||||
{isLoading && <Loader2 className="h-4 w-4 animate-spin" />}
|
||||
Transfer
|
||||
</UpdateAction>
|
||||
) : (
|
||||
)}
|
||||
|
||||
{!canTransferRoadmap && (
|
||||
<UpdateAction
|
||||
disabled={isUpdateDisabled || isLoading}
|
||||
onClick={() => {
|
||||
|
@@ -33,27 +33,31 @@ export interface TeamMemberList extends TeamMemberDocument {
|
||||
|
||||
type ShareTeamMemberListProps = {
|
||||
teamId: string;
|
||||
setMembers: (members: TeamMemberList[]) => void;
|
||||
members: TeamMemberList[];
|
||||
title?: string;
|
||||
sharedTeamMemberIds: string[];
|
||||
setSharedTeamMemberIds: (sharedTeamMemberIds: string[]) => void;
|
||||
|
||||
membersCache: Map<string, TeamMemberList[]>;
|
||||
isTeamMembersLoading: boolean;
|
||||
setIsTeamMembersLoading: (isLoading: boolean) => void;
|
||||
};
|
||||
|
||||
export function ShareTeamMemberList(props: ShareTeamMemberListProps) {
|
||||
const {
|
||||
setMembers,
|
||||
members,
|
||||
teamId,
|
||||
title = 'Select Members',
|
||||
sharedTeamMemberIds,
|
||||
setSharedTeamMemberIds,
|
||||
teamId,
|
||||
|
||||
membersCache,
|
||||
isTeamMembersLoading: isLoading,
|
||||
setIsTeamMembersLoading: setIsLoading,
|
||||
} = props;
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
async function loadTeamMembers() {
|
||||
if (members?.length > 0) {
|
||||
if (membersCache.has(teamId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -67,21 +71,21 @@ export function ShareTeamMemberList(props: ShareTeamMemberListProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
setMembers(response);
|
||||
membersCache.set(teamId, response);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadTeamMembers().finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, []);
|
||||
}, [teamId]);
|
||||
|
||||
const loadingMembers = isLoading && (
|
||||
<ul className="mt-2 grid grid-cols-3 gap-2.5">
|
||||
{[...Array(3)].map((_, idx) => (
|
||||
<li
|
||||
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="inline-grid w-full">
|
||||
@@ -93,11 +97,13 @@ export function ShareTeamMemberList(props: ShareTeamMemberListProps) {
|
||||
</ul>
|
||||
);
|
||||
|
||||
const members = membersCache.get(teamId) || [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{(members.length > 0 || isLoading) && (
|
||||
<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">
|
||||
<input
|
||||
|
@@ -11,10 +11,22 @@ type TransferToTeamListProps = {
|
||||
|
||||
selectedTeamId: string | null;
|
||||
setSelectedTeamId: (teamId: string | null) => void;
|
||||
|
||||
isTeamMembersLoading: boolean;
|
||||
setIsTeamMembersLoading: (isLoading: boolean) => void;
|
||||
onTeamChange: (teamId: string | null) => void;
|
||||
};
|
||||
|
||||
export function TransferToTeamList(props: TransferToTeamListProps) {
|
||||
const { teams, setTeams, selectedTeamId, setSelectedTeamId } = props;
|
||||
const {
|
||||
teams,
|
||||
setTeams,
|
||||
selectedTeamId,
|
||||
setSelectedTeamId,
|
||||
isTeamMembersLoading,
|
||||
setIsTeamMembersLoading,
|
||||
onTeamChange,
|
||||
} = props;
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
@@ -73,11 +85,17 @@ export function TransferToTeamList(props: TransferToTeamListProps) {
|
||||
<li key={team._id}>
|
||||
<button
|
||||
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'
|
||||
)}
|
||||
disabled={isTeamMembersLoading}
|
||||
onClick={() => {
|
||||
setSelectedTeamId(team._id);
|
||||
if (isSelected) {
|
||||
setSelectedTeamId(null);
|
||||
} else {
|
||||
setSelectedTeamId(team._id);
|
||||
}
|
||||
onTeamChange(team._id);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
|
Reference in New Issue
Block a user