Allow transferring roadmap between teams

pull/5713/head
Kamran Ahmed 9 months ago
parent f718d1895f
commit 359e3e1900
  1. 2
      src/components/Navigation/AccountDropdown.tsx
  2. 2
      src/components/PageProgress.tsx
  3. 18
      src/components/ShareOptions/ShareOptionsModal.tsx
  4. 34
      src/components/ShareOptions/ShareOptionsTab.tsx
  5. 5
      src/components/ShareOptions/ShareSuccess.tsx
  6. 11
      src/components/ShareOptions/TransferToTeamList.tsx

@ -93,7 +93,7 @@ export function AccountDropdown() {
).length; ).length;
return ( return (
<div className="relative z-50 animate-fade-in"> <div className="relative z-[90] animate-fade-in">
{isOnboardingModalOpen && onboardingConfig && ( {isOnboardingModalOpen && onboardingConfig && (
<OnboardingModal <OnboardingModal
onboardingConfig={onboardingConfig} onboardingConfig={onboardingConfig}

@ -28,7 +28,7 @@ export function PageProgress(props: Props) {
return ( return (
<div> <div>
{/* Tailwind based spinner for full page */} {/* Tailwind based spinner for full page */}
<div className="fixed left-0 top-0 z-50 flex h-full w-full items-center justify-center bg-white bg-opacity-75"> <div className="fixed left-0 top-0 z-[100] flex h-full w-full items-center justify-center bg-white bg-opacity-75">
<div className="flex items-center justify-center rounded-md border bg-white px-4 py-2 "> <div className="flex items-center justify-center rounded-md border bg-white px-4 py-2 ">
<Spinner <Spinner
className="h-4 w-4 sm:h-4 sm:w-4" className="h-4 w-4 sm:h-4 sm:w-4"

@ -53,6 +53,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
const toast = useToast(); const toast = useToast();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isTransferringToTeam, setIsTransferringToTeam] = useState(false);
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[]>([]);
@ -71,13 +72,12 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
); );
const [selectedTeamId, setSelectedTeamId] = useState<string | null>(null); const [selectedTeamId, setSelectedTeamId] = useState<string | null>(null);
const canTransferRoadmap = visibility === 'team' && !teamId;
let isUpdateDisabled = false; let isUpdateDisabled = false;
// Disable update button if there are no friends to share with // Disable update button if there are no friends to share with
if (visibility === 'friends' && sharedFriendIds.length === 0) { if (visibility === 'friends' && sharedFriendIds.length === 0) {
isUpdateDisabled = true; isUpdateDisabled = true;
// Disable update button if there are no team to transfer // Disable update button if there are no team to transfer
} else if (canTransferRoadmap && !selectedTeamId) { } else if (isTransferringToTeam && !selectedTeamId) {
isUpdateDisabled = true; isUpdateDisabled = true;
// Disable update button if there are no members to share with // Disable update button if there are no members to share with
} else if ( } else if (
@ -198,6 +198,8 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
</div> </div>
<ShareOptionTabs <ShareOptionTabs
isTransferringToTeam={isTransferringToTeam}
setIsTransferringToTeam={setIsTransferringToTeam}
visibility={visibility} visibility={visibility}
setVisibility={setVisibility} setVisibility={setVisibility}
teamId={teamId} teamId={teamId}
@ -226,6 +228,8 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
/> />
<div className="mt-4 flex grow flex-col"> <div className="mt-4 flex grow flex-col">
{!isTransferringToTeam && (
<>
{visibility === 'public' && ( {visibility === 'public' && (
<div className="flex h-full flex-grow flex-col items-center justify-center rounded-md border bg-gray-50 text-center"> <div className="flex h-full flex-grow flex-col items-center justify-center rounded-md border bg-gray-50 text-center">
<Globe2 className="mb-3 h-10 w-10 text-gray-300" /> <Globe2 className="mb-3 h-10 w-10 text-gray-300" />
@ -242,7 +246,6 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
</p> </p>
</div> </div>
)} )}
{/* For Personal Roadmap */} {/* For Personal Roadmap */}
{visibility === 'friends' && ( {visibility === 'friends' && (
<ShareFriendList <ShareFriendList
@ -264,10 +267,13 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
setIsTeamMembersLoading={setIsTeamMembersLoading} setIsTeamMembersLoading={setIsTeamMembersLoading}
/> />
)} )}
</>
)}
{canTransferRoadmap && ( {isTransferringToTeam && (
<> <>
<TransferToTeamList <TransferToTeamList
currentTeamId={teamId}
teams={teams} teams={teams}
setTeams={setTeams} setTeams={setTeams}
selectedTeamId={selectedTeamId} selectedTeamId={selectedTeamId}
@ -319,7 +325,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
Close Close
</button> </button>
{canTransferRoadmap && ( {isTransferringToTeam && (
<UpdateAction <UpdateAction
disabled={ disabled={
isUpdateDisabled || isLoading || sharedTeamMemberIds.length === 0 isUpdateDisabled || isLoading || sharedTeamMemberIds.length === 0
@ -335,7 +341,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
</UpdateAction> </UpdateAction>
)} )}
{!canTransferRoadmap && ( {!isTransferringToTeam && (
<UpdateAction <UpdateAction
disabled={isUpdateDisabled || isLoading} disabled={isUpdateDisabled || isLoading}
onClick={() => { onClick={() => {

@ -8,6 +8,8 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import type { AllowedRoadmapVisibility } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal'; import type { AllowedRoadmapVisibility } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
import { cn } from '../../lib/classname'; import { cn } from '../../lib/classname';
import { $teamList } from '../../stores/team.ts';
import { useStore } from '@nanostores/react';
export const allowedVisibilityLabels: { export const allowedVisibilityLabels: {
id: AllowedRoadmapVisibility; id: AllowedRoadmapVisibility;
@ -44,15 +46,29 @@ export const allowedVisibilityLabels: {
type ShareOptionTabsProps = { type ShareOptionTabsProps = {
visibility: AllowedRoadmapVisibility; visibility: AllowedRoadmapVisibility;
setVisibility: (visibility: AllowedRoadmapVisibility) => void; setVisibility: (visibility: AllowedRoadmapVisibility) => void;
isTransferringToTeam: boolean;
setIsTransferringToTeam: (isTransferringToTeam: boolean) => void;
teamId?: string; teamId?: string;
onChange: (visibility: AllowedRoadmapVisibility) => void; onChange: (visibility: AllowedRoadmapVisibility) => void;
}; };
export function ShareOptionTabs(props: ShareOptionTabsProps) { export function ShareOptionTabs(props: ShareOptionTabsProps) {
const { visibility, setVisibility, teamId, onChange } = props; const {
isTransferringToTeam,
setIsTransferringToTeam,
visibility,
setVisibility,
teamId,
onChange,
} = props;
const handleClick = (visibility: AllowedRoadmapVisibility) => { const teamList = useStore($teamList);
const handleTabClick = (visibility: AllowedRoadmapVisibility) => {
setIsTransferringToTeam(false);
setVisibility(visibility); setVisibility(visibility);
onChange(visibility); onChange(visibility);
}; };
@ -63,11 +79,9 @@ export function ShareOptionTabs(props: ShareOptionTabsProps) {
{allowedVisibilityLabels.map((v) => { {allowedVisibilityLabels.map((v) => {
if (v.id === 'friends' && teamId) { if (v.id === 'friends' && teamId) {
return null; return null;
} else if (v.id === 'team' && !teamId) {
return null;
} }
const isActive = v.id === visibility; const isActive = !isTransferringToTeam && v.id === visibility;
return ( return (
<li key={v.id}> <li key={v.id}>
<OptionTab <OptionTab
@ -75,21 +89,21 @@ export function ShareOptionTabs(props: ShareOptionTabsProps) {
isActive={isActive} isActive={isActive}
icon={v.icon} icon={v.icon}
onClick={() => { onClick={() => {
handleClick(v.id); handleTabClick(v.id);
}} }}
/> />
</li> </li>
); );
})} })}
</ul> </ul>
{!teamId && ( {(!teamId || teamList.length > 1) && (
<div className="grow"> <div className="grow">
<OptionTab <OptionTab
label="Transfer to team" label="Transfer to team"
icon={ArrowLeftRight} icon={ArrowLeftRight}
isActive={visibility === 'team'} isActive={isTransferringToTeam}
onClick={() => { onClick={() => {
handleClick('team'); setIsTransferringToTeam(true);
}} }}
className='border-red-300 text-red-600 hover:border-red-200 hover:bg-red-50 data-[active="true"]:border-red-600 data-[active="true"]:bg-red-600 data-[active="true"]:text-white' className='border-red-300 text-red-600 hover:border-red-200 hover:bg-red-50 data-[active="true"]:border-red-600 data-[active="true"]:bg-red-600 data-[active="true"]:text-white'
/> />
@ -115,7 +129,7 @@ function OptionTab(props: OptionTabProps) {
className={cn( className={cn(
'flex items-center justify-center gap-2 rounded-md border px-3 py-2 text-sm text-black hover:border-gray-300 hover:bg-gray-100', 'flex items-center justify-center gap-2 rounded-md border px-3 py-2 text-sm text-black hover:border-gray-300 hover:bg-gray-100',
'data-[active="true"]:border-gray-500 data-[active="true"]:bg-gray-200 data-[active="true"]:text-black', 'data-[active="true"]:border-gray-500 data-[active="true"]:bg-gray-200 data-[active="true"]:text-black',
className className,
)} )}
data-active={isActive} data-active={isActive}
disabled={isActive} disabled={isActive}

@ -82,6 +82,8 @@ export function ShareSuccess(props: ShareSuccessProps) {
</p> </p>
)} )}
{visibility === 'public' && (
<>
<div className="mt-2 border-t pt-2"> <div className="mt-2 border-t pt-2">
<p className="text-sm text-gray-400"> <p className="text-sm text-gray-400">
You can also embed this roadmap on your website. You can also embed this roadmap on your website.
@ -98,9 +100,6 @@ export function ShareSuccess(props: ShareSuccessProps) {
/> />
</div> </div>
</div> </div>
{visibility === 'public' && (
<>
<div className="-mx-4 mt-4 flex items-center gap-1.5"> <div className="-mx-4 mt-4 flex items-center gap-1.5">
<span className="h-px grow bg-gray-300" /> <span className="h-px grow bg-gray-300" />
<span className="px-2 text-xs uppercase text-gray-400">Or</span> <span className="px-2 text-xs uppercase text-gray-400">Or</span>

@ -9,6 +9,7 @@ type TransferToTeamListProps = {
teams: UserTeamItem[]; teams: UserTeamItem[];
setTeams: (teams: UserTeamItem[]) => void; setTeams: (teams: UserTeamItem[]) => void;
currentTeamId?: string;
selectedTeamId: string | null; selectedTeamId: string | null;
setSelectedTeamId: (teamId: string | null) => void; setSelectedTeamId: (teamId: string | null) => void;
@ -24,6 +25,7 @@ export function TransferToTeamList(props: TransferToTeamListProps) {
selectedTeamId, selectedTeamId,
setSelectedTeamId, setSelectedTeamId,
isTeamMembersLoading, isTeamMembersLoading,
currentTeamId,
setIsTeamMembersLoading, setIsTeamMembersLoading,
onTeamChange, onTeamChange,
} = props; } = props;
@ -38,7 +40,7 @@ export function TransferToTeamList(props: TransferToTeamListProps) {
} }
const { response, error } = await httpGet<UserTeamItem[]>( const { response, error } = await httpGet<UserTeamItem[]>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-teams` `${import.meta.env.PUBLIC_API_URL}/v1-get-user-teams`,
); );
if (error || !response) { if (error || !response) {
toast.error(error?.message || 'Something went wrong'); toast.error(error?.message || 'Something went wrong');
@ -46,7 +48,7 @@ export function TransferToTeamList(props: TransferToTeamListProps) {
} }
setTeams( setTeams(
response.filter((team) => ['admin', 'manager'].includes(team.role)) response.filter((team) => ['admin', 'manager'].includes(team.role)),
); );
} }
@ -80,13 +82,16 @@ export function TransferToTeamList(props: TransferToTeamListProps) {
<ul className="mt-2 grid grid-cols-3 gap-1.5"> <ul className="mt-2 grid grid-cols-3 gap-1.5">
{teams.map((team) => { {teams.map((team) => {
const isSelected = team._id === selectedTeamId; const isSelected = team._id === selectedTeamId;
if (team._id === currentTeamId) {
return null;
}
return ( return (
<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 disabled:cursor-not-allowed disabled:opacity-70', '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} disabled={isTeamMembersLoading}
onClick={() => { onClick={() => {

Loading…
Cancel
Save