From 20be28653fbedb02ec81853d351d4a800f1eafc0 Mon Sep 17 00:00:00 2001 From: guangwu Date: Sat, 4 May 2024 11:10:54 +0800 Subject: [PATCH 1/7] fix: following typo (#5308) * fix: typo * fix: gap --------- Co-authored-by: Arik Chakma --- src/data/roadmaps/typescript/content/100-typescript/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/roadmaps/typescript/content/100-typescript/index.md b/src/data/roadmaps/typescript/content/100-typescript/index.md index 6074c52d2..80c9dc4eb 100644 --- a/src/data/roadmaps/typescript/content/100-typescript/index.md +++ b/src/data/roadmaps/typescript/content/100-typescript/index.md @@ -9,7 +9,7 @@ The main benefits of using TypeScript include: - Improved Maintainability - Backwards Compatibility -Learn more from the folowing links: +Learn more from the following links: - [Overview of TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html) - [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html) From 2b20996134362e2f24e6dccbc2bc09440c8eb2db Mon Sep 17 00:00:00 2001 From: Kamran Ahmed Date: Sun, 5 May 2024 18:50:27 +0100 Subject: [PATCH 2/7] Show proper error on open ai model --- .../GenerateRoadmap/OpenAISettings.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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/env.d.ts b/src/env.d.ts index d52cd1cc6..7218154c2 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -4,6 +4,7 @@ interface ImportMetaEnv { GITHUB_SHA: string; PUBLIC_API_URL: string; + PUBLIC_APP_URL: string; PUBLIC_AVATAR_BASE_URL: string; PUBLIC_EDITOR_APP_URL: string; } diff --git a/src/lib/road-card.ts b/src/lib/road-card.ts new file mode 100644 index 000000000..8aec82c1d --- /dev/null +++ b/src/lib/road-card.ts @@ -0,0 +1,17 @@ +export async function getRoadCard( + version: 'tall' | 'wide', + userId: string, + variant: 'dark' | 'light', + roadmaps: string = '', +) { + const url = new URL( + `${import.meta.env.PUBLIC_API_URL}/v1-badge/${version}/${userId}`, + ); + url.searchParams.set('variant', variant); + if (roadmaps) { + url.searchParams.set('roadmaps', roadmaps); + } + + const response = await fetch(url.toString()); + return response.text(); +} diff --git a/src/pages/card/[version]/[userId].ts b/src/pages/card/[version]/[userId].ts new file mode 100644 index 000000000..a3a974b60 --- /dev/null +++ b/src/pages/card/[version]/[userId].ts @@ -0,0 +1,34 @@ +import type { APIRoute } from 'astro'; +import { getDefaultOpenGraphImageBuffer } from '../../../lib/open-graph'; +import { getRoadCard } from '../../../lib/road-card'; + +export const prerender = false; + +type Params = { + version: 'tall' | 'wide'; + userId: string; +}; + +export const GET: APIRoute = async (context) => { + const { userId, version } = context.params; + + if (!userId || !version) { + const buffer = await getDefaultOpenGraphImageBuffer(); + return new Response(buffer, { + headers: { + 'Content-Type': 'image/png', + }, + }); + } + + const searchParams = new URLSearchParams(context.url.searchParams); + const variant = (searchParams.get('variant') as 'dark' | 'light') || 'dark'; + const roadmaps = searchParams.get('roadmaps') || ''; + + const svg = await getRoadCard(version, userId, variant, roadmaps); + return new Response(svg, { + headers: { + 'Content-Type': 'image/svg+xml', + }, + }); +}; From 66e4793032a79b4666b0e8f3c308f6bfaafa999f Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Tue, 7 May 2024 19:56:44 +0600 Subject: [PATCH 6/7] feat: team personal progress only (#5586) * feat: team personal progress only * fix: default false --- src/components/CreateTeam/CreateTeamForm.tsx | 23 +++-- src/components/CreateTeam/Step1.tsx | 24 ++++-- .../TeamActivity/TeamActivityPage.tsx | 84 +++++++++++++------ .../TeamSettings/UpdateTeamForm.tsx | 40 +++++++-- tsconfig.json | 1 + 5 files changed, 121 insertions(+), 51 deletions(-) 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/TeamActivity/TeamActivityPage.tsx b/src/components/TeamActivity/TeamActivityPage.tsx index 1c124214e..2b4a4a4ce 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?.topicIds?.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?.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() + ); + }); + + enrichedUsers.push(...usersWithUniqueActivities); + } - 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() - ); - }); + 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 +

+ )} +