computer-scienceangular-roadmapbackend-roadmapblockchain-roadmapdba-roadmapdeveloper-roadmapdevops-roadmapfrontend-roadmapgo-roadmaphactoberfestjava-roadmapjavascript-roadmapnodejs-roadmappython-roadmapqa-roadmapreact-roadmaproadmapstudy-planvue-roadmapweb3-roadmap
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
9.3 KiB
312 lines
9.3 KiB
1 year ago
|
import { type ReactNode, useCallback, useState } from 'react';
|
||
|
import { Globe2, Loader2, Lock } from 'lucide-react';
|
||
|
import { type ListFriendsResponse, ShareFriendList } from './ShareFriendList';
|
||
|
import { TransferToTeamList } from './TransferToTeamList';
|
||
|
import { ShareOptionTabs } from './ShareOptionsTab';
|
||
|
import {
|
||
|
ShareTeamMemberList,
|
||
|
type TeamMemberList,
|
||
|
} from './ShareTeamMemberList';
|
||
|
import { CopyRoadmapLink } from './CopyRoadmapLink';
|
||
|
import { useToast } from '../../hooks/use-toast';
|
||
|
import type { AllowedRoadmapVisibility } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
|
||
|
import { httpPatch } from '../../lib/http';
|
||
|
import { Modal } from '../Modal';
|
||
|
import { cn } from '../../lib/classname';
|
||
|
import type { UserTeamItem } from '../TeamDropdown/TeamDropdown';
|
||
|
|
||
|
export type OnShareSettingsUpdate = (options: {
|
||
|
visibility: AllowedRoadmapVisibility;
|
||
|
sharedTeamMemberIds: string[];
|
||
|
sharedFriendIds: string[];
|
||
|
}) => void;
|
||
|
|
||
|
type ShareOptionsModalProps = {
|
||
|
onClose: () => void;
|
||
|
visibility: AllowedRoadmapVisibility;
|
||
|
sharedFriendIds?: string[];
|
||
|
sharedTeamMemberIds?: string[];
|
||
|
teamId?: string;
|
||
|
roadmapId?: string;
|
||
|
|
||
|
onShareSettingsUpdate: OnShareSettingsUpdate;
|
||
|
};
|
||
|
|
||
|
export function ShareOptionsModal(props: ShareOptionsModalProps) {
|
||
|
const {
|
||
|
roadmapId,
|
||
|
onClose,
|
||
|
visibility: defaultVisibility,
|
||
|
sharedTeamMemberIds: defaultSharedMemberIds = [],
|
||
|
sharedFriendIds: defaultSharedFriendIds = [],
|
||
|
teamId,
|
||
|
onShareSettingsUpdate,
|
||
|
} = props;
|
||
|
|
||
|
const toast = useToast();
|
||
|
|
||
|
const [isLoading, setIsLoading] = useState(false);
|
||
|
const [isSettingsUpdated, setIsSettingsUpdated] = useState(false);
|
||
|
const [friends, setFriends] = useState<ListFriendsResponse>([]);
|
||
|
const [teams, setTeams] = useState<UserTeamItem[]>([]);
|
||
|
const [members, setMembers] = useState<TeamMemberList[]>([]);
|
||
|
|
||
|
const [visibility, setVisibility] = useState(defaultVisibility);
|
||
|
const [sharedTeamMemberIds, setSharedTeamMemberIds] = useState<string[]>(
|
||
|
defaultSharedMemberIds
|
||
|
);
|
||
|
const [sharedFriendIds, setSharedFriendIds] = useState<string[]>(
|
||
|
defaultSharedFriendIds
|
||
|
);
|
||
|
const [selectedTeamId, setSelectedTeamId] = useState<string | null>(null);
|
||
|
|
||
|
const canTransferRoadmap = visibility === 'team' && !teamId;
|
||
|
let isUpdateDisabled = false;
|
||
|
// Disable update button if there are no friends to share with
|
||
|
if (visibility === 'friends' && sharedFriendIds.length === 0) {
|
||
|
isUpdateDisabled = true;
|
||
|
// Disable update button if there are no team to transfer
|
||
|
} else if (canTransferRoadmap && !selectedTeamId) {
|
||
|
isUpdateDisabled = true;
|
||
|
// Disable update button if there are no members to share with
|
||
|
} else if (
|
||
|
visibility === 'team' &&
|
||
|
teamId &&
|
||
|
sharedTeamMemberIds.length === 0
|
||
|
) {
|
||
|
isUpdateDisabled = true;
|
||
|
}
|
||
|
|
||
|
const handleShareChange: OnShareSettingsUpdate = async ({
|
||
|
sharedFriendIds,
|
||
|
visibility,
|
||
|
sharedTeamMemberIds,
|
||
|
}) => {
|
||
|
setIsLoading(true);
|
||
|
|
||
|
if (visibility === 'friends' && sharedFriendIds.length === 0) {
|
||
|
toast.error('Please select at least one friend');
|
||
|
return;
|
||
|
} else if (
|
||
|
visibility === 'team' &&
|
||
|
teamId &&
|
||
|
sharedTeamMemberIds.length === 0
|
||
|
) {
|
||
|
toast.error('Please select at least one member');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const { response, error } = await httpPatch(
|
||
|
`${
|
||
|
import.meta.env.PUBLIC_API_URL
|
||
|
}/v1-update-roadmap-visibility/${roadmapId}`,
|
||
|
{
|
||
|
visibility,
|
||
|
sharedFriendIds,
|
||
|
sharedTeamMemberIds,
|
||
|
}
|
||
|
);
|
||
|
|
||
|
if (error) {
|
||
|
toast.error(error?.message || 'Something went wrong, please try again');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
setIsLoading(false);
|
||
|
setIsSettingsUpdated(true);
|
||
|
onShareSettingsUpdate({ sharedFriendIds, visibility, sharedTeamMemberIds });
|
||
|
};
|
||
|
|
||
|
const handleTransferToTeam = useCallback(
|
||
|
async (teamId: string) => {
|
||
|
if (!roadmapId) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
setIsLoading(true);
|
||
|
const { response, error } = await httpPatch(
|
||
|
`${import.meta.env.PUBLIC_API_URL}/v1-transfer-roadmap/${roadmapId}`,
|
||
|
{
|
||
|
teamId,
|
||
|
}
|
||
|
);
|
||
|
|
||
|
if (error) {
|
||
|
setIsLoading(false);
|
||
|
toast.error(error?.message || 'Something went wrong, please try again');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
window.location.reload();
|
||
|
},
|
||
|
[roadmapId]
|
||
|
);
|
||
|
|
||
|
if (isSettingsUpdated) {
|
||
|
return (
|
||
|
<Modal
|
||
|
onClose={onClose}
|
||
|
wrapperClassName="max-w-lg"
|
||
|
bodyClassName="p-4 flex flex-col"
|
||
|
>
|
||
|
<CopyRoadmapLink roadmapId={roadmapId!} onClose={onClose} />
|
||
|
</Modal>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return (
|
||
|
<Modal
|
||
|
onClose={() => {
|
||
|
if (isLoading) {
|
||
|
return;
|
||
|
}
|
||
|
onClose();
|
||
|
}}
|
||
|
wrapperClassName="max-w-3xl"
|
||
|
bodyClassName="p-4 flex flex-col min-h-[400px]"
|
||
|
>
|
||
|
<div className="mb-4">
|
||
|
<h3 className="mb-1 text-xl font-semibold">Update Sharing Settings</h3>
|
||
|
<p className="text-sm text-gray-500">
|
||
|
Pick and modify who can access this roadmap.
|
||
|
</p>
|
||
|
</div>
|
||
|
|
||
|
<ShareOptionTabs
|
||
|
visibility={visibility}
|
||
|
setVisibility={setVisibility}
|
||
|
teamId={teamId}
|
||
|
onChange={(visibility) => {
|
||
|
setSelectedTeamId(null);
|
||
|
|
||
|
if (['me', 'public'].includes(visibility)) {
|
||
|
setSharedTeamMemberIds([]);
|
||
|
setSharedFriendIds([]);
|
||
|
} else if (visibility === 'friends') {
|
||
|
setSharedFriendIds(
|
||
|
defaultSharedFriendIds.length > 0 ? defaultSharedFriendIds : []
|
||
|
);
|
||
|
} else if (visibility === 'team' && teamId) {
|
||
|
setSharedTeamMemberIds(
|
||
|
defaultSharedMemberIds?.length > 0 ? defaultSharedMemberIds : []
|
||
|
);
|
||
|
setSharedFriendIds([]);
|
||
|
} else {
|
||
|
setSharedFriendIds([]);
|
||
|
setSharedTeamMemberIds([]);
|
||
|
}
|
||
|
}}
|
||
|
/>
|
||
|
|
||
|
<div className="mt-4 flex grow flex-col">
|
||
|
{visibility === 'public' && (
|
||
|
<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" />
|
||
|
<p className="font-medium text-gray-500">
|
||
|
Anyone with the link can access.
|
||
|
</p>
|
||
|
</div>
|
||
|
)}
|
||
|
{visibility === 'me' && (
|
||
|
<div className="flex h-full flex-grow flex-col items-center justify-center rounded-md border bg-gray-50 text-center">
|
||
|
<Lock className="mb-3 h-10 w-10 text-gray-300" />
|
||
|
<p className="font-medium text-gray-500">
|
||
|
Only you will be able to access.
|
||
|
</p>
|
||
|
</div>
|
||
|
)}
|
||
|
|
||
|
{/* For Personal Roadmap */}
|
||
|
{visibility === 'friends' && (
|
||
|
<ShareFriendList
|
||
|
friends={friends}
|
||
|
setFriends={setFriends}
|
||
|
sharedFriendIds={sharedFriendIds}
|
||
|
setSharedFriendIds={setSharedFriendIds}
|
||
|
/>
|
||
|
)}
|
||
|
{canTransferRoadmap && (
|
||
|
<TransferToTeamList
|
||
|
teams={teams}
|
||
|
setTeams={setTeams}
|
||
|
selectedTeamId={selectedTeamId}
|
||
|
setSelectedTeamId={setSelectedTeamId}
|
||
|
/>
|
||
|
)}
|
||
|
|
||
|
{/* For Team Roadmap */}
|
||
|
{visibility === 'team' && teamId && (
|
||
|
<ShareTeamMemberList
|
||
|
teamId={teamId}
|
||
|
sharedTeamMemberIds={sharedTeamMemberIds}
|
||
|
setSharedTeamMemberIds={setSharedTeamMemberIds}
|
||
|
members={members}
|
||
|
setMembers={setMembers}
|
||
|
/>
|
||
|
)}
|
||
|
</div>
|
||
|
|
||
|
<div className="mt-2 flex items-center justify-between gap-1.5">
|
||
|
<button
|
||
|
className="flex items-center justify-center gap-1.5 rounded-md border px-3.5 py-1.5 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-75"
|
||
|
disabled={isLoading}
|
||
|
onClick={onClose}
|
||
|
>
|
||
|
Close
|
||
|
</button>
|
||
|
|
||
|
{canTransferRoadmap ? (
|
||
|
<UpdateAction
|
||
|
disabled={isUpdateDisabled || isLoading}
|
||
|
onClick={() => {
|
||
|
handleTransferToTeam(selectedTeamId!).then(() => null);
|
||
|
}}
|
||
|
>
|
||
|
{isLoading && <Loader2 className="h-4 w-4 animate-spin" />}
|
||
|
Transfer
|
||
|
</UpdateAction>
|
||
|
) : (
|
||
|
<UpdateAction
|
||
|
disabled={isUpdateDisabled || isLoading}
|
||
|
onClick={() => {
|
||
|
handleShareChange({
|
||
|
visibility,
|
||
|
sharedTeamMemberIds:
|
||
|
visibility === 'team' ? sharedTeamMemberIds : [],
|
||
|
sharedFriendIds:
|
||
|
visibility === 'friends' ? sharedFriendIds : [],
|
||
|
});
|
||
|
}}
|
||
|
>
|
||
|
{isLoading && <Loader2 className="h-4 w-4 animate-spin" />}
|
||
|
Update Sharing Settings
|
||
|
</UpdateAction>
|
||
|
)}
|
||
|
</div>
|
||
|
</Modal>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function UpdateAction(props: {
|
||
|
onClick: () => void;
|
||
|
disabled?: boolean;
|
||
|
children: ReactNode;
|
||
|
className?: string;
|
||
|
}) {
|
||
|
const { onClick, disabled, children, className } = props;
|
||
|
|
||
|
return (
|
||
|
<button
|
||
|
className={cn(
|
||
|
'flex min-w-[120px] items-center justify-center gap-1.5 rounded-md border border-gray-900 bg-gray-900 px-4 py-2 text-white hover:bg-gray-800 disabled:cursor-not-allowed disabled:opacity-75',
|
||
|
disabled && 'border-gray-700 bg-gray-700 text-white hover:bg-gray-700',
|
||
|
className
|
||
|
)}
|
||
|
disabled={disabled}
|
||
|
onClick={onClick}
|
||
|
>
|
||
|
{children}
|
||
|
</button>
|
||
|
);
|
||
|
}
|