mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-07-31 22:40:19 +02:00
Use the same add roadmap modal
This commit is contained in:
@@ -14,13 +14,13 @@ export type TeamResourceConfig = {
|
|||||||
}[];
|
}[];
|
||||||
|
|
||||||
type RoadmapSelectorProps = {
|
type RoadmapSelectorProps = {
|
||||||
team: TeamDocument;
|
teamId: string;
|
||||||
teamResourceConfig: TeamResourceConfig;
|
teamResourceConfig: TeamResourceConfig;
|
||||||
setTeamResourceConfig: (config: TeamResourceConfig) => void;
|
setTeamResourceConfig: (config: TeamResourceConfig) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function RoadmapSelector(props: RoadmapSelectorProps) {
|
export function RoadmapSelector(props: RoadmapSelectorProps) {
|
||||||
const { team, teamResourceConfig = [], setTeamResourceConfig } = props;
|
const { teamId, teamResourceConfig = [], setTeamResourceConfig } = props;
|
||||||
|
|
||||||
const [showSelectRoadmapModal, setShowSelectRoadmapModal] = useState(false);
|
const [showSelectRoadmapModal, setShowSelectRoadmapModal] = useState(false);
|
||||||
const [allRoadmaps, setAllRoadmaps] = useState<PageType[]>([]);
|
const [allRoadmaps, setAllRoadmaps] = useState<PageType[]>([]);
|
||||||
@@ -51,15 +51,15 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteResource(roadmapId: string) {
|
async function deleteResource(roadmapId: string) {
|
||||||
if (!team?._id) {
|
if (!teamId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pageProgressMessage.set(`Deleting resource`);
|
pageProgressMessage.set(`Deleting resource`);
|
||||||
const { error, response } = await httpPut<TeamResourceConfig>(
|
const { error, response } = await httpPut<TeamResourceConfig>(
|
||||||
`${import.meta.env.PUBLIC_API_URL}/v1-delete-team-resource-config/${
|
`${
|
||||||
team._id
|
import.meta.env.PUBLIC_API_URL
|
||||||
}`,
|
}/v1-delete-team-resource-config/${teamId}`,
|
||||||
{
|
{
|
||||||
resourceId: roadmapId,
|
resourceId: roadmapId,
|
||||||
resourceType: 'roadmap',
|
resourceType: 'roadmap',
|
||||||
@@ -83,17 +83,17 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function addTeamResource(roadmapId: string) {
|
async function addTeamResource(roadmapId: string) {
|
||||||
if (!team?._id) {
|
if (!teamId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pageProgressMessage.set(`Adding roadmap to team`);
|
pageProgressMessage.set(`Adding roadmap to team`);
|
||||||
const { error, response } = await httpPut<TeamResourceConfig>(
|
const { error, response } = await httpPut<TeamResourceConfig>(
|
||||||
`${import.meta.env.PUBLIC_API_URL}/v1-update-team-resource-config/${
|
`${
|
||||||
team._id
|
import.meta.env.PUBLIC_API_URL
|
||||||
}`,
|
}/v1-update-team-resource-config/${teamId}`,
|
||||||
{
|
{
|
||||||
teamId: team._id,
|
teamId: teamId,
|
||||||
resourceId: roadmapId,
|
resourceId: roadmapId,
|
||||||
resourceType: 'roadmap',
|
resourceType: 'roadmap',
|
||||||
removed: [],
|
removed: [],
|
||||||
@@ -119,7 +119,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
|
|||||||
onClose={() => setChangingRoadmapId('')}
|
onClose={() => setChangingRoadmapId('')}
|
||||||
resourceId={changingRoadmapId}
|
resourceId={changingRoadmapId}
|
||||||
resourceType={'roadmap'}
|
resourceType={'roadmap'}
|
||||||
teamId={team?._id!}
|
teamId={teamId}
|
||||||
setTeamResourceConfig={setTeamResourceConfig}
|
setTeamResourceConfig={setTeamResourceConfig}
|
||||||
defaultRemovedItems={
|
defaultRemovedItems={
|
||||||
teamResourceConfig.find((c) => c.resourceId === changingRoadmapId)
|
teamResourceConfig.find((c) => c.resourceId === changingRoadmapId)
|
||||||
@@ -132,7 +132,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
|
|||||||
onClose={() => setShowSelectRoadmapModal(false)}
|
onClose={() => setShowSelectRoadmapModal(false)}
|
||||||
teamResourceConfig={teamResourceConfig}
|
teamResourceConfig={teamResourceConfig}
|
||||||
allRoadmaps={allRoadmaps}
|
allRoadmaps={allRoadmaps}
|
||||||
teamId={team?._id!}
|
teamId={teamId}
|
||||||
onRoadmapAdd={(roadmapId) => {
|
onRoadmapAdd={(roadmapId) => {
|
||||||
addTeamResource(roadmapId).finally(() => {
|
addTeamResource(roadmapId).finally(() => {
|
||||||
pageProgressMessage.set('');
|
pageProgressMessage.set('');
|
||||||
|
@@ -24,7 +24,7 @@ export function Step2(props: Step2Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RoadmapSelector
|
<RoadmapSelector
|
||||||
team={team}
|
teamId={team._id!}
|
||||||
teamResourceConfig={teamResourceConfig}
|
teamResourceConfig={teamResourceConfig}
|
||||||
setTeamResourceConfig={setTeamResourceConfig}
|
setTeamResourceConfig={setTeamResourceConfig}
|
||||||
/>
|
/>
|
||||||
@@ -41,18 +41,6 @@ export function Step2(props: Step2Props) {
|
|||||||
<span className="mr-1">←</span>
|
<span className="mr-1">←</span>
|
||||||
Previous Step
|
Previous Step
|
||||||
</button>
|
</button>
|
||||||
<div className={'flex gap-2'}>
|
|
||||||
{teamResourceConfig.length === 0 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onNext}
|
|
||||||
className={
|
|
||||||
'rounded-md border border-gray-300 bg-white px-4 py-2 text-gray-500 hover:border-gray-400 hover:text-black'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Skip for Now
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={teamResourceConfig.length === 0}
|
disabled={teamResourceConfig.length === 0}
|
||||||
@@ -65,7 +53,6 @@ export function Step2(props: Step2Props) {
|
|||||||
<span className="ml-1">→</span>
|
<span className="ml-1">→</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -139,10 +139,6 @@ export function TeamDropdown() {
|
|||||||
pageLink = `/team/progress?t=${team._id}`;
|
pageLink = `/team/progress?t=${team._id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (team.roadmaps.length === 0) {
|
|
||||||
pageLink = `/team/new?t=${team._id}&s=2`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
|
@@ -5,13 +5,14 @@ import type { TeamResourceConfig } from './CreateTeam/RoadmapSelector';
|
|||||||
import { httpGet, httpPut } from '../lib/http';
|
import { httpGet, httpPut } from '../lib/http';
|
||||||
import { pageProgressMessage } from '../stores/page';
|
import { pageProgressMessage } from '../stores/page';
|
||||||
import ExternalLinkIcon from '../icons/external-link.svg';
|
import ExternalLinkIcon from '../icons/external-link.svg';
|
||||||
|
import RoadmapIcon from '../icons/roadmap.svg';
|
||||||
import PlusIcon from '../icons/plus.svg';
|
import PlusIcon from '../icons/plus.svg';
|
||||||
import type { PageType } from './CommandMenu/CommandMenu';
|
import type { PageType } from './CommandMenu/CommandMenu';
|
||||||
import { UpdateTeamResourceModal } from './CreateTeam/UpdateTeamResourceModal';
|
import { UpdateTeamResourceModal } from './CreateTeam/UpdateTeamResourceModal';
|
||||||
import { AddTeamRoadmap } from './AddTeamRoadmap';
|
|
||||||
import { useStore } from '@nanostores/preact';
|
import { useStore } from '@nanostores/preact';
|
||||||
import { $canManageCurrentTeam } from '../stores/team';
|
import { $canManageCurrentTeam } from '../stores/team';
|
||||||
import {useToast} from "../hooks/use-toast";
|
import { useToast } from '../hooks/use-toast';
|
||||||
|
import { SelectRoadmapModal } from './CreateTeam/SelectRoadmapModal';
|
||||||
|
|
||||||
export function TeamRoadmaps() {
|
export function TeamRoadmaps() {
|
||||||
const { t: teamId } = getUrlParams();
|
const { t: teamId } = getUrlParams();
|
||||||
@@ -20,6 +21,7 @@ export function TeamRoadmaps() {
|
|||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [removingRoadmapId, setRemovingRoadmapId] = useState<string>('');
|
const [removingRoadmapId, setRemovingRoadmapId] = useState<string>('');
|
||||||
const [isAddingRoadmap, setIsAddingRoadmap] = useState(false);
|
const [isAddingRoadmap, setIsAddingRoadmap] = useState(false);
|
||||||
const [changingRoadmapId, setChangingRoadmapId] = useState<string>('');
|
const [changingRoadmapId, setChangingRoadmapId] = useState<string>('');
|
||||||
@@ -83,12 +85,14 @@ export function TeamRoadmaps() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
Promise.all([
|
Promise.all([
|
||||||
loadTeam(teamId),
|
loadTeam(teamId),
|
||||||
loadTeamResourceConfig(teamId),
|
loadTeamResourceConfig(teamId),
|
||||||
loadAllRoadmaps(),
|
loadAllRoadmaps(),
|
||||||
]).finally(() => {
|
]).finally(() => {
|
||||||
pageProgressMessage.set('');
|
pageProgressMessage.set('');
|
||||||
|
setIsLoading(false);
|
||||||
});
|
});
|
||||||
}, [teamId]);
|
}, [teamId]);
|
||||||
|
|
||||||
@@ -97,6 +101,7 @@ export function TeamRoadmaps() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toast.loading('Deleting roadmap');
|
||||||
pageProgressMessage.set(`Deleting roadmap from team`);
|
pageProgressMessage.set(`Deleting roadmap from team`);
|
||||||
const { error, response } = await httpPut<TeamResourceConfig>(
|
const { error, response } = await httpPut<TeamResourceConfig>(
|
||||||
`${import.meta.env.PUBLIC_API_URL}/v1-delete-team-resource-config/${
|
`${import.meta.env.PUBLIC_API_URL}/v1-delete-team-resource-config/${
|
||||||
@@ -117,6 +122,35 @@ export function TeamRoadmaps() {
|
|||||||
setResourceConfigs(response);
|
setResourceConfigs(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onAdd(roadmapId: string) {
|
||||||
|
if (!teamId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.loading('Adding roadmap');
|
||||||
|
pageProgressMessage.set('Adding roadmap');
|
||||||
|
setIsLoading(true);
|
||||||
|
const { error, response } = await httpPut<TeamResourceConfig>(
|
||||||
|
`${
|
||||||
|
import.meta.env.PUBLIC_API_URL
|
||||||
|
}/v1-update-team-resource-config/${teamId}`,
|
||||||
|
{
|
||||||
|
teamId: teamId,
|
||||||
|
resourceId: roadmapId,
|
||||||
|
resourceType: 'roadmap',
|
||||||
|
removed: [],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error || !response) {
|
||||||
|
toast.error(error?.message || 'Error adding roadmap');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setResourceConfigs(response);
|
||||||
|
toast.success('Roadmap added');
|
||||||
|
}
|
||||||
|
|
||||||
async function onRemove(resourceId: string) {
|
async function onRemove(resourceId: string) {
|
||||||
pageProgressMessage.set('Removing roadmap');
|
pageProgressMessage.set('Removing roadmap');
|
||||||
|
|
||||||
@@ -129,26 +163,56 @@ export function TeamRoadmaps() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addRoadmapModal = isAddingRoadmap && (
|
||||||
|
<SelectRoadmapModal
|
||||||
|
onClose={() => setIsAddingRoadmap(false)}
|
||||||
|
teamResourceConfig={resourceConfigs}
|
||||||
|
allRoadmaps={allRoadmaps}
|
||||||
|
teamId={teamId}
|
||||||
|
onRoadmapAdd={(roadmapId) => {
|
||||||
|
onAdd(roadmapId).finally(() => {
|
||||||
|
pageProgressMessage.set('');
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onRoadmapRemove={(roadmapId) => {
|
||||||
|
if (confirm('Are you sure you want to remove this roadmap?')) {
|
||||||
|
onRemove(roadmapId).finally(() => {});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resourceConfigs.length === 0 && !isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center p-4 py-20">
|
||||||
|
{addRoadmapModal}
|
||||||
|
<img
|
||||||
|
alt="roadmap"
|
||||||
|
src={RoadmapIcon}
|
||||||
|
className="mb-4 h-24 w-24 opacity-10"
|
||||||
|
/>
|
||||||
|
<h3 className="mb-1 text-2xl font-bold text-gray-900">No roadmaps</h3>
|
||||||
|
<p className="text-base text-gray-500">
|
||||||
|
{canManageCurrentTeam
|
||||||
|
? 'Add a roadmap to start tracking your team'
|
||||||
|
: 'Ask your team admin to add some roadmaps'}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{canManageCurrentTeam && (
|
||||||
|
<button
|
||||||
|
className="mt-4 rounded-lg bg-black px-4 py-2 font-medium text-white hover:bg-gray-900"
|
||||||
|
onClick={() => setIsAddingRoadmap(true)}
|
||||||
|
>
|
||||||
|
Add roadmap
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{isAddingRoadmap && (
|
{addRoadmapModal}
|
||||||
<AddTeamRoadmap
|
|
||||||
onMakeChanges={(roadmapId) => {
|
|
||||||
setChangingRoadmapId(roadmapId);
|
|
||||||
setIsAddingRoadmap(false);
|
|
||||||
}}
|
|
||||||
teamId={team?._id!}
|
|
||||||
setResourceConfigs={setResourceConfigs}
|
|
||||||
allRoadmaps={allRoadmaps}
|
|
||||||
availableRoadmaps={allRoadmaps.filter((r) => {
|
|
||||||
const isAlreadyAdded = resourceConfigs.find(
|
|
||||||
(c) => c.resourceId === r.id
|
|
||||||
);
|
|
||||||
return !isAlreadyAdded;
|
|
||||||
})}
|
|
||||||
onClose={() => setIsAddingRoadmap(false)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className={'grid grid-cols-1 gap-3 sm:grid-cols-2'}>
|
<div className={'grid grid-cols-1 gap-3 sm:grid-cols-2'}>
|
||||||
{changingRoadmapId && (
|
{changingRoadmapId && (
|
||||||
<UpdateTeamResourceModal
|
<UpdateTeamResourceModal
|
||||||
@@ -198,8 +262,8 @@ export function TeamRoadmaps() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ canManageCurrentTeam && (
|
{canManageCurrentTeam && (
|
||||||
<div className={'flex w-full justify-between pt-2 pb-3 px-3'}>
|
<div className={'flex w-full justify-between px-3 pb-3 pt-2'}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={
|
className={
|
||||||
@@ -219,13 +283,7 @@ export function TeamRoadmaps() {
|
|||||||
className={
|
className={
|
||||||
'text-xs text-red-500 underline hover:text-black focus:outline-none disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:text-red-500'
|
'text-xs text-red-500 underline hover:text-black focus:outline-none disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:text-red-500'
|
||||||
}
|
}
|
||||||
disabled={resourceConfigs.length === 1}
|
|
||||||
onClick={() => setRemovingRoadmapId(resourceId)}
|
onClick={() => setRemovingRoadmapId(resourceId)}
|
||||||
title={
|
|
||||||
resourceConfigs.length === 1
|
|
||||||
? 'You must have at least one roadmap.'
|
|
||||||
: 'Delete roadmap from team'
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
|
@@ -37,7 +37,7 @@ export function Toaster(props: Props) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
$toastMessage.set(undefined);
|
$toastMessage.set(undefined);
|
||||||
}}
|
}}
|
||||||
className={`fixed bottom-5 left-1/2 max-w-[300px] animate-fade-slide-up min-w-[300px] sm:min-w-[auto] z-50`}
|
className={`fixed bottom-5 left-1/2 z-50 min-w-[300px] max-w-[300px] animate-fade-slide-up sm:min-w-[auto]`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex -translate-x-1/2 transform cursor-pointer items-center gap-2 rounded-md border border-gray-200 bg-white py-3 pl-4 pr-5 text-black shadow-md hover:bg-gray-50`}
|
className={`flex -translate-x-1/2 transform cursor-pointer items-center gap-2 rounded-md border border-gray-200 bg-white py-3 pl-4 pr-5 text-black shadow-md hover:bg-gray-50`}
|
||||||
|
Reference in New Issue
Block a user