feat: implement team activity stream (#5565)

* wip

* feat: implement team activity page

* feat: add pagination

* fix: add max height

* Team activity updates

* Remove invalid activityes

* Team activity page

* fix: team roadmap versions not working

* Add team activity items

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
pull/5578/head
Arik Chakma 7 months ago committed by GitHub
parent 8ceedadd22
commit f2a2ac9ec8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      src/components/Activity/ActivityTopicsModal.tsx
  2. 1
      src/components/Authenticator/authenticator.ts
  3. 2
      src/components/CreateTeam/Step4.tsx
  4. 14
      src/components/FrameRenderer/renderer.ts
  5. 2
      src/components/HeroSection/HeroRoadmaps.tsx
  6. 2
      src/components/Navigation/DropdownTeamList.tsx
  7. 2
      src/components/Notification/NotificationPage.tsx
  8. 8
      src/components/RespondInviteForm.tsx
  9. 195
      src/components/TeamActivity/TeamActivityItem.tsx
  10. 189
      src/components/TeamActivity/TeamActivityPage.tsx
  11. 128
      src/components/TeamActivity/TeamActivityTopicsModal.tsx
  12. 23
      src/components/TeamActivity/TeamEmptyStream.tsx
  13. 2
      src/components/TeamDropdown/TeamDropdown.tsx
  14. 4
      src/components/TeamProgress/MemberProgressModal.tsx
  15. 8
      src/components/TeamSidebar.tsx
  16. 25
      src/components/TeamVersions/TeamVersions.tsx
  17. 2
      src/components/TeamsList.tsx
  18. 1397
      src/data/roadmaps/backend/backend.json
  19. 3128
      src/data/roadmaps/mlops/mlops.json
  20. 4
      src/lib/resource-progress.ts
  21. 15
      src/pages/team/activity.astro
  22. 8
      src/pages/team/progress.astro

@ -107,7 +107,7 @@ export function ActivityTopicsModal(props: ActivityTopicDetailsProps) {
/> />
</a> </a>
</span> </span>
<ul className="flex flex-col gap-1"> <ul className="flex max-h-[50vh] flex-col gap-1 overflow-y-auto max-md:max-h-full">
{topicIds.map((topicId) => { {topicIds.map((topicId) => {
const topicTitle = topicTitles[topicId] || 'Unknown Topic'; const topicTitle = topicTitles[topicId] || 'Unknown Topic';

@ -42,6 +42,7 @@ function handleGuest() {
'/account', '/account',
'/team', '/team',
'/team/progress', '/team/progress',
'/team/activity',
'/team/roadmaps', '/team/roadmaps',
'/team/new', '/team/new',
'/team/members', '/team/members',

@ -15,7 +15,7 @@ export function Step4({ team }: Step4Props) {
Your team has been created. Happy learning! Your team has been created. Happy learning!
</p> </p>
<a <a
href={`/team/progress?t=${team._id}`} href={`/team/activity?t=${team._id}`}
className="mt-4 rounded-md bg-black px-5 py-1.5 text-sm text-white" className="mt-4 rounded-md bg-black px-5 py-1.5 text-sm text-white"
> >
View Team View Team

@ -1,20 +1,19 @@
import { wireframeJSONToSVG } from 'roadmap-renderer'; import { wireframeJSONToSVG } from 'roadmap-renderer';
import { httpPost } from '../../lib/http';
import { isLoggedIn } from '../../lib/jwt'; import { isLoggedIn } from '../../lib/jwt';
import type {
ResourceProgressType,
ResourceType,
} from '../../lib/resource-progress';
import { import {
refreshProgressCounters, refreshProgressCounters,
renderResourceProgress, renderResourceProgress,
renderTopicProgress, renderTopicProgress,
updateResourceProgress, updateResourceProgress,
} from '../../lib/resource-progress'; } from '../../lib/resource-progress';
import type {
ResourceProgressType,
ResourceType,
} from '../../lib/resource-progress';
import { pageProgressMessage } from '../../stores/page'; import { pageProgressMessage } from '../../stores/page';
import { showLoginPopup } from '../../lib/popup'; import { showLoginPopup } from '../../lib/popup';
import { replaceChildren } from '../../lib/dom.ts'; import { replaceChildren } from '../../lib/dom.ts';
import {setUrlParams} from "../../lib/browser.ts"; import { setUrlParams } from '../../lib/browser.ts';
export class Renderer { export class Renderer {
resourceId: string; resourceId: string;
@ -94,7 +93,6 @@ export class Renderer {
}) })
.then((svg) => { .then((svg) => {
replaceChildren(this.containerEl!, svg); replaceChildren(this.containerEl!, svg);
// this.containerEl?.replaceChildren(svg);
}) })
.then(() => { .then(() => {
return renderResourceProgress( return renderResourceProgress(
@ -143,7 +141,7 @@ export class Renderer {
const newJsonFileSlug = newJsonUrl.split('/').pop()?.replace('.json', ''); const newJsonFileSlug = newJsonUrl.split('/').pop()?.replace('.json', '');
const type = this.resourceType[0]; // r for roadmap, b for best-practices const type = this.resourceType[0]; // r for roadmap, b for best-practices
setUrlParams({ [type]: newJsonFileSlug! }) setUrlParams({ [type]: newJsonFileSlug! });
this.jsonToSvg(newJsonUrl)?.then(() => {}); this.jsonToSvg(newJsonUrl)?.then(() => {});
} }

@ -201,7 +201,7 @@ export function HeroRoadmaps(props: ProgressListProps) {
Team{' '} Team{' '}
<a <a
className="mx-1 font-medium underline underline-offset-2 transition-colors hover:text-gray-300" className="mx-1 font-medium underline underline-offset-2 transition-colors hover:text-gray-300"
href={`/team/progress?t=${currentTeam?.id}`} href={`/team/activity?t=${currentTeam?.id}`}
> >
{teamName} {teamName}
</a> </a>

@ -73,7 +73,7 @@ export function DropdownTeamList(props: DropdownTeamListProps) {
if (team.status === 'invited') { if (team.status === 'invited') {
pageLink = `/respond-invite?i=${team.memberId}`; pageLink = `/respond-invite?i=${team.memberId}`;
} else if (team.status === 'joined') { } else if (team.status === 'joined') {
pageLink = `/team/progress?t=${team._id}`; pageLink = `/team/activity?t=${team._id}`;
} }
return ( return (

@ -47,7 +47,7 @@ export function NotificationPage() {
} }
if (status === 'accept') { if (status === 'accept') {
window.location.href = `/team/progress?t=${response.teamId}`; window.location.href = `/team/activity?t=${response.teamId}`;
} else { } else {
window.dispatchEvent( window.dispatchEvent(
new CustomEvent('refresh-notification', { new CustomEvent('refresh-notification', {

@ -75,7 +75,7 @@ export function RespondInviteForm() {
window.location.href = '/'; window.location.href = '/';
return; return;
} }
window.location.href = `/team/progress?t=${response.teamId}`; window.location.href = `/team/activity?t=${response.teamId}`;
} }
if (isLoadingInvite) { if (isLoadingInvite) {
@ -106,7 +106,7 @@ export function RespondInviteForm() {
return ( return (
<div className="container text-center"> <div className="container text-center">
<BuildingIcon className="mx-auto mb-4 mt-24 w-20 opacity-20" /> <BuildingIcon className="mx-auto mb-4 mt-24 w-20 h-20 opacity-20" />
<h2 className={'mb-1 text-2xl font-bold'}>Join Team</h2> <h2 className={'mb-1 text-2xl font-bold'}>Join Team</h2>
<p className="mb-3 text-base leading-6 text-gray-600"> <p className="mb-3 text-base leading-6 text-gray-600">
@ -139,7 +139,7 @@ export function RespondInviteForm() {
pageProgressMessage.set(''); pageProgressMessage.set('');
}) })
} }
className="flex-grow cursor-pointer rounded-lg bg-gray-200 px-3 py-2 text-center" className="flex-grow cursor-pointer rounded-lg hover:bg-gray-300 bg-gray-200 px-3 py-2 text-center"
> >
Accept Accept
</button> </button>
@ -150,7 +150,7 @@ export function RespondInviteForm() {
pageProgressMessage.set(''); pageProgressMessage.set('');
}) })
} }
className="flex-grow cursor-pointer rounded-lg bg-red-500 px-3 py-2 text-white disabled:opacity-40" className="flex-grow cursor-pointer rounded-lg bg-red-500 hover:bg-red-600 px-3 py-2 text-white disabled:opacity-40"
> >
Reject Reject
</button> </button>

@ -0,0 +1,195 @@
import { useState } from 'react';
import { getRelativeTimeString } from '../../lib/date';
import type { TeamStreamActivity } from './TeamActivityPage';
import { ChevronsDown, ChevronsUp } from 'lucide-react';
type TeamActivityItemProps = {
onTopicClick?: (activity: TeamStreamActivity) => void;
user: {
activities: TeamStreamActivity[];
_id: string;
name: string;
avatar?: string | undefined;
username?: string | undefined;
};
};
export function TeamActivityItem(props: TeamActivityItemProps) {
const { user, onTopicClick } = props;
const { activities } = user;
const [showAll, setShowAll] = useState(false);
const resourceLink = (activity: TeamStreamActivity) => {
const {
resourceId,
resourceTitle,
resourceType,
isCustomResource,
resourceSlug,
} = activity;
const resourceUrl =
resourceType === 'question'
? `/questions/${resourceId}`
: resourceType === 'best-practice'
? `/best-practices/${resourceId}`
: isCustomResource && resourceType === 'roadmap'
? `/r/${resourceSlug}`
: `/${resourceId}`;
return (
<a
className="font-medium underline transition-colors hover:cursor-pointer hover:text-black"
target="_blank"
href={resourceUrl}
>
{resourceTitle}
</a>
);
};
const timeAgo = (date: string | Date) => (
<span className="ml-1 text-xs text-gray-400">
{getRelativeTimeString(new Date(date).toISOString())}
</span>
);
const userAvatar = user.avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${user.avatar}`
: '/images/default-avatar.png';
const username = (
<>
<img
className="mr-1 inline-block h-5 w-5 rounded-full"
src={userAvatar}
alt={user.name}
/>
<span className="font-medium">{user?.name || 'Unknown'}</span>{' '}
</>
);
if (activities.length === 1) {
const activity = activities[0];
const { actionType, topicIds } = activity;
const topicCount = topicIds?.length || 0;
return (
<li
key={user._id}
className="flex items-center flex-wrap gap-1 rounded-md border px-2 py-2.5 text-sm"
>
{actionType === 'in_progress' && (
<>
{username} started{' '}
<button
className="font-medium underline underline-offset-2 hover:text-black"
onClick={() => onTopicClick?.(activity)}
>
{topicCount} topic{topicCount > 1 ? 's' : ''}
</button>{' '}
in {resourceLink(activity)} {timeAgo(activity.updatedAt)}
</>
)}
{actionType === 'done' && (
<>
{username} completed{' '}
<button
className="font-medium underline underline-offset-2 hover:text-black"
onClick={() => onTopicClick?.(activity)}
>
{topicCount} topic{topicCount > 1 ? 's' : ''}
</button>{' '}
in {resourceLink(activity)} {timeAgo(activity.updatedAt)}
</>
)}
{actionType === 'answered' && (
<>
{username} answered {topicCount} question
{topicCount > 1 ? 's' : ''} in {resourceLink(activity)}{' '}
{timeAgo(activity.updatedAt)}
</>
)}
</li>
);
}
const uniqueResourcesCount = new Set(
activities.map((activity) => activity.resourceId),
).size;
const activityLimit = showAll ? activities.length : 5;
return (
<li key={user._id} className="rounded-md border overflow-hidden">
<h3 className="flex flex-wrap items-center gap-1 bg-gray-100 px-2 py-2.5 text-sm">
{username} has {activities.length} updates in {uniqueResourcesCount}{' '}
resources
</h3>
<div className="py-3">
<ul className="flex flex-col gap-2 ml-2 sm:ml-[36px]">
{activities.slice(0, activityLimit).map((activity) => {
const { actionType, topicIds } = activity;
const topicCount = topicIds?.length || 0;
return (
<li key={activity._id} className="text-sm text-gray-600">
{actionType === 'in_progress' && (
<>
Started{' '}
<button
className="font-medium underline underline-offset-2 hover:text-black"
onClick={() => onTopicClick?.(activity)}
>
{topicCount} topic{topicCount > 1 ? 's' : ''}
</button>{' '}
in {resourceLink(activity)} {timeAgo(activity.updatedAt)}
</>
)}
{actionType === 'done' && (
<>
Completed{' '}
<button
className="font-medium underline underline-offset-2 hover:text-black"
onClick={() => onTopicClick?.(activity)}
>
{topicCount} topic{topicCount > 1 ? 's' : ''}
</button>{' '}
in {resourceLink(activity)} {timeAgo(activity.updatedAt)}
</>
)}
{actionType === 'answered' && (
<>
Answered {topicCount} question
{topicCount > 1 ? 's' : ''} in {resourceLink(activity)}{' '}
{timeAgo(activity.updatedAt)}
</>
)}
</li>
);
})}
</ul>
{activities.length > 5 && (
<button
className="mt-3 flex items-center gap-2 rounded-md border border-gray-300 p-1 text-xs uppercase tracking-wide text-gray-600 transition-colors hover:border-black hover:bg-black hover:text-white"
onClick={() => setShowAll(!showAll)}
>
{showAll ? (
<>
<ChevronsUp size={14} />
Show less
</>
) : (
<>
<ChevronsDown size={14} />
Show more
</>
)}
</button>
)}
</div>
</li>
);
}

@ -0,0 +1,189 @@
import { useEffect, useState, useMemo } from 'react';
import { useToast } from '../../hooks/use-toast';
import { getUrlParams } from '../../lib/browser';
import { httpGet } from '../../lib/http';
import type { ResourceType } from '../../lib/resource-progress';
import type { AllowedActivityActionType } from '../Activity/ActivityStream';
import { pageProgressMessage } from '../../stores/page';
import { TeamActivityItem } from './TeamActivityItem';
import { TeamActivityTopicsModal } from './TeamActivityTopicsModal';
import { TeamEmptyStream } from './TeamEmptyStream';
import { Pagination } from '../Pagination/Pagination';
export type TeamStreamActivity = {
_id?: string;
resourceType: ResourceType | 'question';
resourceId: string;
resourceTitle: string;
resourceSlug?: string;
isCustomResource?: boolean;
actionType: AllowedActivityActionType;
topicIds?: string[];
createdAt: Date;
updatedAt: Date;
};
export interface TeamActivityStreamDocument {
_id?: string;
teamId: string;
userId: string;
activity: TeamStreamActivity[];
createdAt: Date;
updatedAt: Date;
}
type GetTeamActivityResponse = {
data: {
users: {
_id: string;
name: string;
avatar?: string;
username?: string;
}[];
activities: TeamActivityStreamDocument[];
};
totalCount: number;
totalPages: number;
currPage: number;
perPage: number;
};
export function TeamActivityPage() {
const { t: teamId } = getUrlParams();
const toast = useToast();
const [isLoading, setIsLoading] = useState(true);
const [selectedActivity, setSelectedActivity] =
useState<TeamStreamActivity | null>(null);
const [teamActivities, setTeamActivities] = useState<GetTeamActivityResponse>(
{
data: {
users: [],
activities: [],
},
totalCount: 0,
totalPages: 0,
currPage: 1,
perPage: 21,
},
);
const [currPage, setCurrPage] = useState(1);
const getTeamProgress = async (currPage: number = 1) => {
const { response, error } = await httpGet<GetTeamActivityResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-team-activity/${teamId}`,
{
currPage,
},
);
if (error || !response) {
toast.error(error?.message || 'Failed to get team activity');
return;
}
setTeamActivities(response);
setCurrPage(response.currPage);
};
useEffect(() => {
if (!teamId) {
return;
}
getTeamProgress().then(() => {
pageProgressMessage.set('');
setIsLoading(false);
});
}, [teamId]);
const { users, activities } = teamActivities?.data;
const usersWithActivities = useMemo(() => {
const validActivities = activities.filter((activity) => {
return (
activity.activity.length > 0 &&
activity.activity.some((t) => (t?.topicIds?.length || 0) > 0)
);
});
return users
.map((user) => {
const userActivities = validActivities
.filter((activity) => activity.userId === user._id)
.flatMap((activity) => activity.activity)
.filter((activity) => (activity?.topicIds?.length || 0) > 0)
.sort((a, b) => {
return (
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
);
});
return {
...user,
activities: userActivities,
};
})
.filter((user) => user.activities.length > 0)
.sort((a, b) => {
return (
new Date(b.activities[0].updatedAt).getTime() -
new Date(a.activities[0].updatedAt).getTime()
);
});
}, [users, activities]);
if (!teamId) {
window.location.href = '/';
return;
}
if (isLoading) {
return null;
}
return (
<>
{selectedActivity && (
<TeamActivityTopicsModal
activity={selectedActivity}
onClose={() => setSelectedActivity(null)}
/>
)}
{usersWithActivities.length > 0 ? (
<>
<h3 className="mb-4 flex w-full items-center justify-between text-xs uppercase text-gray-400">
Team Activity
</h3>
<ul className="mb-4 mt-2 flex flex-col gap-3">
{usersWithActivities.map((user) => {
return (
<TeamActivityItem
key={user._id}
user={user}
onTopicClick={setSelectedActivity}
/>
);
})}
</ul>
<Pagination
currPage={currPage}
totalPages={teamActivities.totalPages}
totalCount={teamActivities.totalCount}
perPage={teamActivities.perPage}
onPageChange={(page) => {
setCurrPage(page);
pageProgressMessage.set('Loading...');
getTeamProgress(page).finally(() => {
pageProgressMessage.set('');
});
}}
/>
</>
) : (
<TeamEmptyStream teamId={teamId} />
)}
</>
);
}

@ -0,0 +1,128 @@
import { useEffect, useState } from 'react';
import { httpPost } from '../../lib/http';
import { Modal } from '../Modal.tsx';
import { ModalLoader } from '../UserProgress/ModalLoader.tsx';
import { ArrowUpRight, BookOpen, Check } from 'lucide-react';
import type { TeamStreamActivity } from './TeamActivityPage.tsx';
type TeamActivityTopicsModalProps = {
activity: TeamStreamActivity;
onClose: () => void;
};
export function TeamActivityTopicsModal(props: TeamActivityTopicsModalProps) {
const { activity, onClose } = props;
const {
resourceId,
resourceType,
isCustomResource,
topicIds = [],
actionType,
} = activity;
const [isLoading, setIsLoading] = useState(true);
const [topicTitles, setTopicTitles] = useState<Record<string, string>>({});
const [error, setError] = useState<string | null>(null);
const loadTopicTitles = async () => {
setIsLoading(true);
setError(null);
const { response, error } = await httpPost(
`${import.meta.env.PUBLIC_API_URL}/v1-get-topic-titles`,
{
resourceId,
resourceType,
isCustomResource,
topicIds,
},
);
if (error || !response) {
setError(error?.message || 'Failed to load topic titles');
setIsLoading(false);
return;
}
setTopicTitles(response);
setIsLoading(false);
};
useEffect(() => {
loadTopicTitles().finally(() => {
setIsLoading(false);
});
}, []);
if (isLoading || error) {
return (
<ModalLoader
error={error!}
text={'Loading topics..'}
isLoading={isLoading}
/>
);
}
let pageUrl = '';
if (resourceType === 'roadmap') {
pageUrl = isCustomResource ? `/r/${resourceId}` : `/${resourceId}`;
} else if (resourceType === 'best-practice') {
pageUrl = `/best-practices/${resourceId}`;
} else {
pageUrl = `/questions/${resourceId}`;
}
return (
<Modal
onClose={() => {
onClose();
setError(null);
setIsLoading(false);
}}
>
<div className={`popup-body relative rounded-lg bg-white p-4 shadow`}>
<span className="mb-2 flex items-center justify-between text-lg font-semibold capitalize">
<span className="flex items-center gap-2">
{actionType.replace('_', ' ')}
</span>
<a
href={pageUrl}
target="_blank"
className="flex items-center gap-1 rounded-md border border-transparent py-0.5 pl-2 pr-1 text-sm font-normal text-gray-400 transition-colors hover:border-black hover:bg-black hover:text-white"
>
Visit Page{' '}
<ArrowUpRight
size={16}
strokeWidth={2}
className="relative top-px"
/>
</a>
</span>
<ul className="flex max-h-[50vh] flex-col gap-1 overflow-y-auto max-md:max-h-full">
{topicIds.map((topicId) => {
const topicTitle = topicTitles[topicId] || 'Unknown Topic';
const ActivityIcon =
actionType === 'done'
? Check
: actionType === 'in_progress'
? BookOpen
: Check;
return (
<li key={topicId} className="flex items-start gap-2">
<ActivityIcon
strokeWidth={3}
className="relative top-[4px] text-green-500"
size={16}
/>
{topicTitle}
</li>
);
})}
</ul>
</div>
</Modal>
);
}

@ -0,0 +1,23 @@
import { Activity, List, ListTodo } from 'lucide-react';
type TeamActivityItemProps = {
teamId: string;
};
export function TeamEmptyStream(props: TeamActivityItemProps) {
const { teamId } = props;
return (
<div className="rounded-md">
<div className="flex flex-col items-center p-7 text-center sm:p-14">
<ListTodo className="mb-4 h-[60px] w-[60px] opacity-10 sm:h-[60px] sm:w-[60px]" />
<h2 className="text-lg font-semibold sm:text-lg">No Activity</h2>
<p className="my-1 max-w-[400px] text-balance text-sm text-gray-500 sm:my-2 sm:text-base">
Team activity will appear here once members start tracking their
progress.
</p>
</div>
</div>
);
}

@ -162,7 +162,7 @@ export function TeamDropdown() {
if (team.status === 'invited') { if (team.status === 'invited') {
pageLink = `/respond-invite?i=${team.memberId}`; pageLink = `/respond-invite?i=${team.memberId}`;
} else if (team.status === 'joined') { } else if (team.status === 'joined') {
pageLink = `/team/progress?t=${team._id}`; pageLink = `/team/activity?t=${team._id}`;
} }
return ( return (

@ -57,7 +57,9 @@ export function MemberProgressModal(props: ProgressMapProps) {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const toast = useToast(); const toast = useToast();
let resourceJsonUrl = 'https://roadmap.sh'; let resourceJsonUrl = import.meta.env.DEV
? 'http://localhost:3000'
: 'https://roadmap.sh';
if (resourceType === 'roadmap') { if (resourceType === 'roadmap') {
resourceJsonUrl += `/${resourceId}.json`; resourceJsonUrl += `/${resourceId}.json`;
} else { } else {

@ -9,7 +9,7 @@ import { SubmitFeedbackPopup } from './Feedback/SubmitFeedbackPopup';
import { ChevronDownIcon } from './ReactIcons/ChevronDownIcon.tsx'; import { ChevronDownIcon } from './ReactIcons/ChevronDownIcon.tsx';
import { GroupIcon } from './ReactIcons/GroupIcon.tsx'; import { GroupIcon } from './ReactIcons/GroupIcon.tsx';
import { TeamProgressIcon } from './ReactIcons/TeamProgressIcon.tsx'; import { TeamProgressIcon } from './ReactIcons/TeamProgressIcon.tsx';
import { MapIcon, MessageCircle } from 'lucide-react'; import { BarChart2, MapIcon, MessageCircle } from 'lucide-react';
import { CogIcon } from './ReactIcons/CogIcon.tsx'; import { CogIcon } from './ReactIcons/CogIcon.tsx';
type TeamSidebarProps = { type TeamSidebarProps = {
@ -25,6 +25,12 @@ export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
const { teamId } = useTeamId(); const { teamId } = useTeamId();
const sidebarLinks = [ const sidebarLinks = [
{
title: 'Activity',
href: `/team/activity?t=${teamId}`,
id: 'activity',
icon: BarChart2,
},
{ {
title: 'Progress', title: 'Progress',
href: `/team/progress?t=${teamId}`, href: `/team/progress?t=${teamId}`,

@ -107,20 +107,29 @@ export function TeamVersions(props: TeamVersionsProps) {
useEffect(() => { useEffect(() => {
clearResourceProgress(); clearResourceProgress();
// teams have customizations. Assigning #customized-roadmap to roadmapSvgWrap
// makes those customizations visible and removes extra boxes
const roadmapSvgWrap: HTMLElement =
document.getElementById('resource-svg-wrap')?.parentElement ||
document.createElement('div');
if (!selectedTeamVersion) { if (!selectedTeamVersion) {
deleteUrlParam('t'); deleteUrlParam('t');
renderResourceProgress(resourceType, resourceId).then(); renderResourceProgress(resourceType, resourceId).then();
return;
}
setUrlParams({ t: selectedTeamVersion.team._id! }); roadmapSvgWrap.id = '';
} else {
setUrlParams({ t: selectedTeamVersion.team._id! });
renderResourceProgress(resourceType, resourceId).then(() => { renderResourceProgress(resourceType, resourceId).then(() => {
selectedTeamVersion.config?.removed?.forEach((topic) => { selectedTeamVersion.config?.removed?.forEach((topic) => {
renderTopicProgress(topic, 'removed'); renderTopicProgress(topic, 'removed');
});
refreshProgressCounters();
roadmapSvgWrap.id = 'customized-roadmap';
}); });
refreshProgressCounters(); }
});
}, [selectedTeamVersion]); }, [selectedTeamVersion]);
if (isPreparing) { if (isPreparing) {

@ -64,7 +64,7 @@ export function TeamsList() {
if (team.status === 'invited') { if (team.status === 'invited') {
pageLink = `/respond-invite?i=${team.memberId}`; pageLink = `/respond-invite?i=${team.memberId}`;
} else if (team.status === 'joined') { } else if (team.status === 'joined') {
pageLink = `/team/progress?t=${team._id}`; pageLink = `/team/activity?t=${team._id}`;
} }
return ( return (

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -309,11 +309,11 @@ export async function renderResourceProgress(
} }
function getMatchingElements( function getMatchingElements(
quries: string[], queries: string[],
parentElement: Document | SVGElement | HTMLDivElement = document, parentElement: Document | SVGElement | HTMLDivElement = document,
): Element[] { ): Element[] {
const matchingElements: Element[] = []; const matchingElements: Element[] = [];
quries.forEach((query) => { queries.forEach((query) => {
parentElement.querySelectorAll(query).forEach((element) => { parentElement.querySelectorAll(query).forEach((element) => {
matchingElements.push(element); matchingElements.push(element);
}); });

@ -0,0 +1,15 @@
---
import { TeamSidebar } from '../../components/TeamSidebar';
import { TeamActivityPage } from '../../components/TeamActivity/TeamActivityPage';
import AccountLayout from '../../layouts/AccountLayout.astro';
---
<AccountLayout
title='Team Activities'
noIndex={true}
initialLoadingMessage='Loading activity'
>
<TeamSidebar activePageId='activity' client:load>
<TeamActivityPage client:only='react' />
</TeamSidebar>
</AccountLayout>

@ -4,8 +4,12 @@ import { TeamProgressPage } from '../../components/TeamProgress/TeamProgressPage
import AccountLayout from '../../layouts/AccountLayout.astro'; import AccountLayout from '../../layouts/AccountLayout.astro';
--- ---
<AccountLayout title='Team Progress' noIndex={true} initialLoadingMessage='Loading Progress'> <AccountLayout
title='Team Progress'
noIndex={true}
initialLoadingMessage='Loading Progress'
>
<TeamSidebar activePageId='progress' client:load> <TeamSidebar activePageId='progress' client:load>
<TeamProgressPage client:only="react" /> <TeamProgressPage client:only='react' />
</TeamSidebar> </TeamSidebar>
</AccountLayout> </AccountLayout>

Loading…
Cancel
Save