diff --git a/src/components/CreateTeam/CreateTeamForm.tsx b/src/components/CreateTeam/CreateTeamForm.tsx index b06df0c96..84fd33fae 100644 --- a/src/components/CreateTeam/CreateTeamForm.tsx +++ b/src/components/CreateTeam/CreateTeamForm.tsx @@ -9,7 +9,7 @@ import { pageProgressMessage } from '../../stores/page'; import type { TeamResourceConfig } from './RoadmapSelector'; import { Step3 } from './Step3'; import { Step4 } from './Step4'; -import {useToast} from "../../hooks/use-toast"; +import { useToast } from '../../hooks/use-toast'; export interface TeamDocument { _id?: string; @@ -22,6 +22,7 @@ export interface TeamDocument { linkedIn?: string; }; type: ValidTeamType; + personalProgressOnly?: boolean; canMemberSendInvite: boolean; teamSize?: ValidTeamSize; createdAt: Date; @@ -40,10 +41,10 @@ export function CreateTeamForm() { async function loadTeam( teamIdToFetch: string, - requiredStepIndex: number | string + requiredStepIndex: number | string, ) { const { response, error } = await httpGet( - `${import.meta.env.PUBLIC_API_URL}/v1-get-team/${teamIdToFetch}` + `${import.meta.env.PUBLIC_API_URL}/v1-get-team/${teamIdToFetch}`, ); if (error || !response) { @@ -70,7 +71,7 @@ export function CreateTeamForm() { async function loadTeamResourceConfig(teamId: string) { const { error, response } = await httpGet( - `${import.meta.env.PUBLIC_API_URL}/v1-get-team-resource-config/${teamId}` + `${import.meta.env.PUBLIC_API_URL}/v1-get-team-resource-config/${teamId}`, ); if (error || !Array.isArray(response)) { console.error(error); @@ -96,7 +97,7 @@ export function CreateTeamForm() { }, [teamId, queryStepIndex]); const [selectedTeamType, setSelectedTeamType] = useState( - team?.type || 'company' + team?.type || 'company', ); const [completedSteps, setCompletedSteps] = useState([0]); @@ -191,13 +192,17 @@ export function CreateTeamForm() { return (
-
-

Create Team

-

+

+

Create Team

+

Complete the steps below to create your team

-
+
( - team?.teamSize || ('' as any) + team?.teamSize || ('' as any), ); const handleSubmit = async (e: FormEvent) => { @@ -74,7 +74,7 @@ export function Step1(props: Step1Props) { }), roadmapIds: [], bestPracticeIds: [], - } + }, )); if (error || !response?._id) { @@ -96,7 +96,7 @@ export function Step1(props: Step1Props) { teamSize, linkedInUrl: linkedInUrl || undefined, }), - } + }, )); if (error || (response as any)?.status !== 'ok') { @@ -168,7 +168,10 @@ export function Step1(props: Step1Props) { {selectedTeamType === 'company' && (
-
diff --git a/src/components/GenerateRoadmap/OpenAISettings.tsx b/src/components/GenerateRoadmap/OpenAISettings.tsx index 5cb6f897c..181a8347b 100644 --- a/src/components/GenerateRoadmap/OpenAISettings.tsx +++ b/src/components/GenerateRoadmap/OpenAISettings.tsx @@ -1,4 +1,3 @@ -import { Modal } from '../Modal.tsx'; import { useEffect, useState } from 'react'; import { deleteOpenAIKey, getOpenAIKey, saveOpenAIKey } from '../../lib/jwt.ts'; import { cn } from '../../lib/classname.ts'; @@ -17,7 +16,7 @@ export function OpenAISettings(props: OpenAISettingsProps) { const [defaultOpenAIKey, setDefaultOpenAIKey] = useState(''); - const [hasError, setHasError] = useState(false); + const [error, setError] = useState(''); const [openaiApiKey, setOpenaiApiKey] = useState(''); const [isLoading, setIsLoading] = useState(false); @@ -57,7 +56,7 @@ export function OpenAISettings(props: OpenAISettingsProps) { className="mt-4" onSubmit={async (e) => { e.preventDefault(); - setHasError(false); + setError(''); const normalizedKey = openaiApiKey.trim(); if (!normalizedKey) { @@ -68,7 +67,7 @@ export function OpenAISettings(props: OpenAISettingsProps) { } if (!normalizedKey.startsWith('sk-')) { - setHasError(true); + setError("Invalid OpenAI API key. It should start with 'sk-'"); return; } @@ -81,7 +80,7 @@ export function OpenAISettings(props: OpenAISettingsProps) { ); if (error) { - setHasError(true); + setError(error.message); setIsLoading(false); return; } @@ -100,13 +99,13 @@ export function OpenAISettings(props: OpenAISettingsProps) { className={cn( 'block w-full rounded-md border border-gray-300 px-3 py-2 text-gray-800 transition-colors focus:border-black focus:outline-none', { - 'border-red-500 bg-red-100 focus:border-red-500': hasError, + 'border-red-500 bg-red-100 focus:border-red-500': error, }, )} placeholder="Enter your OpenAI API key" value={openaiApiKey} onChange={(e) => { - setHasError(false); + setError(''); setOpenaiApiKey((e.target as HTMLInputElement).value); }} /> @@ -127,9 +126,9 @@ export function OpenAISettings(props: OpenAISettingsProps) { We do not store your API key on our servers.

- {hasError && ( + {error && (

- Please enter a valid OpenAI API key + {error}

)} diff --git a/src/components/TeamActivity/TeamActivityPage.tsx b/src/components/TeamActivity/TeamActivityPage.tsx index 45de81486..29df4e3f0 100644 --- a/src/components/TeamActivity/TeamActivityPage.tsx +++ b/src/components/TeamActivity/TeamActivityPage.tsx @@ -98,38 +98,70 @@ export function TeamActivityPage() { }, [teamId]); const { users, activities } = teamActivities?.data; - const usersWithActivities = useMemo(() => { - const validActivities = activities.filter((activity) => { + const validActivities = useMemo(() => { + return activities?.filter((activity) => { return ( activity.activity.length > 0 && activity.activity.some((t) => (t?.topicTitles?.length || 0) > 0) ); }); + }, [activities]); + + const sortedUniqueCreatedAt = useMemo(() => { + return new Set( + validActivities + ?.map((activity) => new Date(activity.createdAt).setHours(0, 0, 0, 0)) + .sort((a, b) => { + return new Date(b).getTime() - new Date(a).getTime(); + }), + ); + }, [validActivities]); + + const usersWithActivities = useMemo(() => { + const enrichedUsers: { + _id: string; + name: string; + avatar?: string; + username?: string; + activities: TeamStreamActivity[]; + }[] = []; + + for (const uniqueCreatedAt of sortedUniqueCreatedAt) { + const uniqueActivities = validActivities.filter( + (activity) => + new Date(activity.createdAt).setHours(0, 0, 0, 0) === uniqueCreatedAt, + ); + + const usersWithUniqueActivities = users + .map((user) => { + const userActivities = uniqueActivities + .filter((activity) => activity.userId === user._id) + .flatMap((activity) => activity.activity) + .filter((activity) => (activity?.topicTitles?.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() + ); + }); + + enrichedUsers.push(...usersWithUniqueActivities); + } - return users - .map((user) => { - const userActivities = validActivities - .filter((activity) => activity.userId === user._id) - .flatMap((activity) => activity.activity) - .filter((activity) => (activity?.topicTitles?.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() - ); - }); + return enrichedUsers; }, [users, activities]); if (!teamId) { diff --git a/src/components/TeamSettings/UpdateTeamForm.tsx b/src/components/TeamSettings/UpdateTeamForm.tsx index 4e78137f4..b8ef2ae52 100644 --- a/src/components/TeamSettings/UpdateTeamForm.tsx +++ b/src/components/TeamSettings/UpdateTeamForm.tsx @@ -24,6 +24,7 @@ export function UpdateTeamForm() { const [gitHub, setGitHub] = useState(''); const [teamType, setTeamType] = useState(''); const [teamSize, setTeamSize] = useState(''); + const [personalProgressOnly, setPersonalProgressOnly] = useState(false); const validTeamSizes = [ '0-1', '2-10', @@ -55,11 +56,12 @@ export function UpdateTeamForm() { website, type: teamType, gitHubUrl: gitHub || undefined, + personalProgressOnly, ...(teamType === 'company' && { teamSize, linkedInUrl: linkedIn || undefined, }), - } + }, ); if (error) { @@ -77,7 +79,7 @@ export function UpdateTeamForm() { async function loadTeam() { const { response, error } = await httpGet( - `${import.meta.env.PUBLIC_API_URL}/v1-get-team/${teamId}` + `${import.meta.env.PUBLIC_API_URL}/v1-get-team/${teamId}`, ); if (error || !response) { console.log(error); @@ -90,6 +92,7 @@ export function UpdateTeamForm() { setLinkedIn(response?.links?.linkedIn || ''); setGitHub(response?.links?.github || ''); setTeamType(response.type); + setPersonalProgressOnly(response.personalProgressOnly ?? false); if (response.teamSize) { setTeamSize(response.teamSize); } @@ -205,16 +208,14 @@ export function UpdateTeamForm() { @@ -231,7 +232,7 @@ export function UpdateTeamForm() { + setPersonalProgressOnly((e.target as HTMLInputElement).checked) + } + /> + Members can only see their personal progress + +
+ + {personalProgressOnly && ( +

+ Only admins and managers will be able to see the progress of members +

+ )} +