From f61d360ee77279edf881d5872876839124d3aa70 Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Tue, 25 Jul 2023 21:49:21 +0600 Subject: [PATCH 01/11] Add select roadmap modal (#4253) * wip: roadmap selector modal * wip * fix: typo * fix: prettier * chore: close icon --- src/components/CreateTeam/RoadmapSelector.tsx | 75 +++++++----- .../CreateTeam/SelectRoadmapModal.tsx | 114 ++++++++++++++++++ src/icons/plus-white.svg | 1 + 3 files changed, 160 insertions(+), 30 deletions(-) create mode 100644 src/components/CreateTeam/SelectRoadmapModal.tsx create mode 100644 src/icons/plus-white.svg diff --git a/src/components/CreateTeam/RoadmapSelector.tsx b/src/components/CreateTeam/RoadmapSelector.tsx index bec08ff38..96860b062 100644 --- a/src/components/CreateTeam/RoadmapSelector.tsx +++ b/src/components/CreateTeam/RoadmapSelector.tsx @@ -1,11 +1,12 @@ import { useEffect, useState } from 'preact/hooks'; -import { SearchSelector } from '../SearchSelector'; import { httpGet, httpPut } from '../../lib/http'; import type { PageType } from '../CommandMenu/CommandMenu'; -import SearchIcon from '../../icons/search.svg'; +import PlusIcon from '../../icons/plus.svg'; +import PlusWhiteIcon from '../../icons/plus-white.svg'; import { pageProgressMessage } from '../../stores/page'; import type { TeamDocument } from './CreateTeamForm'; import { UpdateTeamResourceModal } from './UpdateTeamResourceModal'; +import { SelectRoadmapModal } from './SelectRoadmapModal'; export type TeamResourceConfig = { resourceId: string; @@ -22,6 +23,7 @@ type RoadmapSelectorProps = { export function RoadmapSelector(props: RoadmapSelectorProps) { const { team, teamResourceConfig = [], setTeamResourceConfig } = props; + const [showSelectRoadmapModal, setShowSelectRoadmapModal] = useState(false); const [allRoadmaps, setAllRoadmaps] = useState([]); const [changingRoadmapId, setChangingRoadmapId] = useState(''); const [error, setError] = useState(''); @@ -126,42 +128,42 @@ export function RoadmapSelector(props: RoadmapSelectorProps) { } /> )} - - { - const roadmapId = option.value; - addTeamResource(roadmapId).finally(() => { - pageProgressMessage.set(''); - }); - }} - options={allRoadmaps - .filter((roadmap) => { - return !teamResourceConfig - .map((c) => c.resourceId) - .includes(roadmap.id); - }) - .map((roadmap) => ({ - value: roadmap.id, - label: roadmap.title, - }))} - searchInputId={'roadmap-input'} - inputClassName="mt-2 block w-full rounded-md border px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1" - /> + {showSelectRoadmapModal && ( + setShowSelectRoadmapModal(false)} + teamResourceConfig={teamResourceConfig} + allRoadmaps={allRoadmaps} + teamId={team?._id!} + onRoadmapAdd={(roadmapId) => { + addTeamResource(roadmapId).finally(() => { + pageProgressMessage.set(''); + }); + }} + onRoadmapRemove={(roadmapId) => { + onRemove(roadmapId).finally(() => {}); + }} + /> + )} {!teamResourceConfig.length && (
- {'search'} No roadmaps selected.

- Please search and add roadmaps from above + Please search and add roadmaps from below

+
+ +
)} @@ -214,6 +216,19 @@ export function RoadmapSelector(props: RoadmapSelectorProps) { ); })} + )} diff --git a/src/components/CreateTeam/SelectRoadmapModal.tsx b/src/components/CreateTeam/SelectRoadmapModal.tsx new file mode 100644 index 000000000..27fd56d71 --- /dev/null +++ b/src/components/CreateTeam/SelectRoadmapModal.tsx @@ -0,0 +1,114 @@ +import { useEffect, useRef, useState } from 'preact/hooks'; +import { useKeydown } from '../../hooks/use-keydown'; +import { useOutsideClick } from '../../hooks/use-outside-click'; +import type { PageType } from '../CommandMenu/CommandMenu'; +import { useToast } from '../../hooks/use-toast'; +import type { TeamResourceConfig } from './RoadmapSelector'; +import CloseIcon from '../../icons/close.svg'; + +export type SelectRoadmapModalProps = { + teamId: string; + allRoadmaps: PageType[]; + onClose: () => void; + teamResourceConfig: TeamResourceConfig; + onRoadmapAdd: (roadmapId: string) => void; + onRoadmapRemove: (roadmapId: string) => void; +}; + +export function SelectRoadmapModal(props: SelectRoadmapModalProps) { + const { + onClose, + allRoadmaps, + onRoadmapAdd, + onRoadmapRemove, + teamResourceConfig, + } = props; + const toast = useToast(); + const popupBodyEl = useRef(null); + + const [searchResults, setSearchResults] = useState(allRoadmaps); + const [searchText, setSearchText] = useState(''); + + useKeydown('Escape', () => { + onClose(); + }); + + useOutsideClick(popupBodyEl, () => { + onClose(); + }); + + useEffect(() => { + if (searchText.length === 0) { + setSearchResults(allRoadmaps); + return; + } + + const searchResults = allRoadmaps.filter((roadmap) => { + return ( + roadmap.title.toLowerCase().includes(searchText.toLowerCase()) || + roadmap.id.toLowerCase().includes(searchText.toLowerCase()) + ); + }); + setSearchResults(searchResults); + }, [searchText, allRoadmaps]); + + return ( +
+
+ +
+
+ ); +} diff --git a/src/icons/plus-white.svg b/src/icons/plus-white.svg new file mode 100644 index 000000000..4986ebc4b --- /dev/null +++ b/src/icons/plus-white.svg @@ -0,0 +1 @@ + From 8c7fb8cab52468e86c69b93850fe4e5e005be7ae Mon Sep 17 00:00:00 2001 From: Kamran Ahmed Date: Tue, 25 Jul 2023 16:51:34 +0100 Subject: [PATCH 02/11] Copy change --- src/components/CreateTeam/Step0.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/CreateTeam/Step0.tsx b/src/components/CreateTeam/Step0.tsx index cec4e78fa..16ad19114 100644 --- a/src/components/CreateTeam/Step0.tsx +++ b/src/components/CreateTeam/Step0.tsx @@ -87,10 +87,10 @@ export function Step0(props: Step0Props) { validTeamType.value === selectedTeamType ? 'opacity-100' : '' }`} /> - + {validTeamType.label} - + {validTeamType.description} From 3182e2a5993ee69683f9c7fbf7e09469197cefe4 Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Tue, 25 Jul 2023 22:36:49 +0600 Subject: [PATCH 03/11] Show current user progress first (#4255) * wip: progress sorting * chore: show current user progress first * fix: team guard * fix: user progress sort --- src/components/TeamDropdown/TeamDropdown.tsx | 5 ++--- .../TeamProgress/MemberProgressItem.tsx | 2 +- src/components/TeamProgress/TeamProgressPage.tsx | 16 ++++++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/TeamDropdown/TeamDropdown.tsx b/src/components/TeamDropdown/TeamDropdown.tsx index 5fccaa30a..a4124ef7d 100644 --- a/src/components/TeamDropdown/TeamDropdown.tsx +++ b/src/components/TeamDropdown/TeamDropdown.tsx @@ -101,9 +101,8 @@ export function TeamDropdown() { - {progress.resourceTitle} + {progress.resourceTitle} {progress.done} / {progress.total} diff --git a/src/components/TeamProgress/TeamProgressPage.tsx b/src/components/TeamProgress/TeamProgressPage.tsx index 51188d101..0ab12507a 100644 --- a/src/components/TeamProgress/TeamProgressPage.tsx +++ b/src/components/TeamProgress/TeamProgressPage.tsx @@ -10,6 +10,7 @@ import { GroupRoadmapItem } from './GroupRoadmapItem'; import { setUrlParams } from '../../lib/browser'; import { getUrlParams } from '../../lib/browser'; import { $toastMessage } from '../../stores/toast'; +import { useAuth } from '../../hooks/use-auth'; export type UserProgress = { resourceTitle: string; @@ -55,6 +56,7 @@ export function TeamProgressPage() { const [isLoading, setIsLoading] = useState(true); const toast = useToast(); const currentTeam = useStore($currentTeam); + const user = useAuth(); const [teamMembers, setTeamMembers] = useState([]); const [selectedGrouping, setSelectedGrouping] = useState< @@ -70,7 +72,10 @@ export function TeamProgressPage() { return; } - setTeamMembers(response); + const currentUserProgress = response.find((member) => member.email === user?.email) + const otherUserProgresses = response.filter(member => member.email !== user?.email); + const allUserProgresses = currentUserProgress ? [currentUserProgress, ...otherUserProgresses] : otherUserProgresses; + setTeamMembers(allUserProgresses); } useEffect(() => { @@ -134,11 +139,10 @@ export function TeamProgressPage() {
{groupingTypes.map((grouping) => ( +
{ + setShowSelectRoadmapModal(true); + }} + > + {teamResourceConfig.length > 0 && ( +
+

+ {teamResourceConfig.length} roadmaps selected +

+

+ Click to add or change selection +

-
+ )} + + {!teamResourceConfig.length && ( +
+

Click to select roadmaps

+
+ )} + + {'roadmap'} +
+ + {!teamResourceConfig.length && ( +

+ No roadmaps selected. +

)} {teamResourceConfig.length > 0 && ( @@ -216,19 +230,6 @@ export function RoadmapSelector(props: RoadmapSelectorProps) { ); })} - )} diff --git a/src/components/CreateTeam/SelectRoadmapModal.tsx b/src/components/CreateTeam/SelectRoadmapModal.tsx index 27fd56d71..cf4a33856 100644 --- a/src/components/CreateTeam/SelectRoadmapModal.tsx +++ b/src/components/CreateTeam/SelectRoadmapModal.tsx @@ -2,9 +2,9 @@ import { useEffect, useRef, useState } from 'preact/hooks'; import { useKeydown } from '../../hooks/use-keydown'; import { useOutsideClick } from '../../hooks/use-outside-click'; import type { PageType } from '../CommandMenu/CommandMenu'; -import { useToast } from '../../hooks/use-toast'; import type { TeamResourceConfig } from './RoadmapSelector'; import CloseIcon from '../../icons/close.svg'; +import { SelectRoadmapModalItem } from './SelectRoadmapModalItem'; export type SelectRoadmapModalProps = { teamId: string; @@ -23,8 +23,8 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) { onRoadmapRemove, teamResourceConfig, } = props; - const toast = useToast(); const popupBodyEl = useRef(null); + const searchInputEl = useRef(null); const [searchResults, setSearchResults] = useState(allRoadmaps); const [searchText, setSearchText] = useState(''); @@ -37,6 +37,14 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) { onClose(); }); + useEffect(() => { + if (!searchInputEl.current) { + return; + } + + searchInputEl.current.focus(); + }, [searchInputEl]); + useEffect(() => { if (searchText.length === 0) { setSearchResults(allRoadmaps); @@ -52,43 +60,54 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) { setSearchResults(searchResults); }, [searchText, allRoadmaps]); + const roleBasedRoadmaps = searchResults.filter((roadmap) => + roadmap?.metadata?.tags?.includes('role-roadmap') + ); + const skillBasedRoadmaps = searchResults.filter((roadmap) => + roadmap?.metadata?.tags?.includes('skill-roadmap') + ); + return (
-
+
diff --git a/src/components/CreateTeam/SelectRoadmapModalItem.tsx b/src/components/CreateTeam/SelectRoadmapModalItem.tsx new file mode 100644 index 000000000..e2a579e94 --- /dev/null +++ b/src/components/CreateTeam/SelectRoadmapModalItem.tsx @@ -0,0 +1,34 @@ +import type { SelectRoadmapModalProps } from './SelectRoadmapModal'; + +type SelectRoadmapModalItemProps = { + title: string; + isSelected: boolean; + onClick: () => void; +}; + +export function SelectRoadmapModalItem(props: SelectRoadmapModalItemProps) { + const { isSelected, onClick, title } = props; + return ( + + ); +} diff --git a/src/components/CreateTeam/Step2.tsx b/src/components/CreateTeam/Step2.tsx index 723456cd8..1e7c37141 100644 --- a/src/components/CreateTeam/Step2.tsx +++ b/src/components/CreateTeam/Step2.tsx @@ -17,10 +17,9 @@ export function Step2(props: Step2Props) { <>
-

Select Roadmaps

+

Select Roadmaps

- Picks the roadmaps to be made available to your team for tracking. - You can always add more later. + You can always add and customize your roadmaps later.

diff --git a/src/pages/pages.json.ts b/src/pages/pages.json.ts index 49146fb76..ef972fbe1 100644 --- a/src/pages/pages.json.ts +++ b/src/pages/pages.json.ts @@ -16,6 +16,9 @@ export async function get() { url: `/${roadmap.id}`, title: roadmap.frontmatter.briefTitle, group: 'Roadmaps', + metadata: { + tags: roadmap.frontmatter.tags, + }, })), ...bestPractices.map((bestPractice) => ({ id: bestPractice.id, From 5cf286a7535c43022b7abb17201ceae2ef54c089 Mon Sep 17 00:00:00 2001 From: Kamran Ahmed Date: Tue, 25 Jul 2023 18:32:43 +0100 Subject: [PATCH 05/11] Update team sizes and copy --- src/components/CreateTeam/Step1.tsx | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/CreateTeam/Step1.tsx b/src/components/CreateTeam/Step1.tsx index 2be5b7138..2df45c47d 100644 --- a/src/components/CreateTeam/Step1.tsx +++ b/src/components/CreateTeam/Step1.tsx @@ -5,10 +5,12 @@ import type { TeamDocument } from './CreateTeamForm'; import { NextButton } from './NextButton'; export const validTeamSizes = [ - '0-1', - '2-10', - '11-50', - '51-200', + '1-5', + '6-10', + '11-25', + '26-50', + '51-100', + '101-200', '201-500', '501-1000', '1000+', @@ -134,7 +136,7 @@ export function Step1(props: Step1Props) { autofocus={true} id="name" className="mt-2 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1" - placeholder="roadmap.sh" + placeholder="Roadmap Inc." disabled={isLoading} required value={name} @@ -167,7 +169,7 @@ export function Step1(props: Step1Props) { {selectedTeamType === 'company' && (
- Company Size + Tech Team Size