From 5ec93304e7f1af4726f2a42c51453992dafff455 Mon Sep 17 00:00:00 2001 From: Sarah Taylor <150694563+staysgt@users.noreply.github.com> Date: Sun, 8 Mar 2026 19:30:17 -0400 Subject: [PATCH 1/7] #4003 guest projects page --- .../prisma-query-args/projects.query-args.ts | 1 + .../src/transformers/projects.transformer.ts | 1 + .../GuestProjectsPage/GuestProjectsCard.tsx | 100 ++++++++++++++++++ .../GuestProjectsPage/GuestProjectsPage.tsx | 74 +++++++++++++ .../src/pages/ProjectsPage/Projects.tsx | 2 + src/frontend/src/utils/routes.ts | 2 + src/shared/src/types/project-types.ts | 1 + 7 files changed, 181 insertions(+) create mode 100644 src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx create mode 100644 src/frontend/src/pages/GuestProjectsPage/GuestProjectsPage.tsx diff --git a/src/backend/src/prisma-query-args/projects.query-args.ts b/src/backend/src/prisma-query-args/projects.query-args.ts index 1c430b3c8a..a3bed1ef29 100644 --- a/src/backend/src/prisma-query-args/projects.query-args.ts +++ b/src/backend/src/prisma-query-args/projects.query-args.ts @@ -100,6 +100,7 @@ export const getProjectPreviewQueryArgs = (organizationId: string) => abbreviation: true, teams: { select: { + teamType: true, teamId: true, teamName: true } diff --git a/src/backend/src/transformers/projects.transformer.ts b/src/backend/src/transformers/projects.transformer.ts index 85325ae54f..7719a04b03 100644 --- a/src/backend/src/transformers/projects.transformer.ts +++ b/src/backend/src/transformers/projects.transformer.ts @@ -117,6 +117,7 @@ export const projectPreviewTransformer = (project: Prisma.ProjectGetPayload team.teamType), teams: project.teams, workPackages: project.workPackages.map((wp) => ({ ...wp, diff --git a/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx b/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx new file mode 100644 index 0000000000..35644f874a --- /dev/null +++ b/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx @@ -0,0 +1,100 @@ +import { alpha, Box, Card, CardContent, Chip, Stack, Typography, useTheme, useMediaQuery, Link } from '@mui/material'; +import { wbsNamePipe, ProjectPreview, wbsPipe, WbsElementStatus } from 'shared'; +import { datePipe } from '../../utils/pipes'; +import { NERButton } from '../../components/NERButton'; +import { useSingleProject } from '../../hooks/projects.hooks'; +import LoadingIndicator from '../../components/LoadingIndicator'; +import ErrorPage from '../ErrorPage'; +import { Link as RouterLink } from 'react-router-dom'; + +interface ProjectCardProps { + project: ProjectPreview; +} + +const GuestProjectsCard: React.FC = ({ project }) => { + const theme = useTheme(); + const isMobilePortrait = useMediaQuery('(max-width:600px)'); + const { data: singleProject, isLoading, isError, error } = useSingleProject(project.wbsNum); + if (isLoading || !singleProject) return ; + if (isError) return ; + + const activeWorkPackages = project.workPackages.filter((wp) => wp.status === WbsElementStatus.Active); + + return ( + + + + + + + {wbsNamePipe(singleProject)} + + {activeWorkPackages[0] ? ( + + ) : null} + + + Project Lead:{' '} + {singleProject.lead?.firstName && singleProject.lead?.lastName + ? `${singleProject.lead.firstName} ${singleProject.lead.lastName}` + : 'N/A'} + {' • '} + Project Manager:{' '} + {singleProject.manager?.firstName && singleProject.manager?.lastName + ? `${singleProject.manager.firstName} ${singleProject.manager.lastName}` + : 'N/A'} + + + {datePipe(singleProject.startDate) + + ' ⟝ ' + + singleProject.duration + + ' wks ⟞ ' + + datePipe(singleProject.endDate)} + + + + {singleProject.summary} + + + + Learn more + + + + + + ); +}; + +export default GuestProjectsCard; diff --git a/src/frontend/src/pages/GuestProjectsPage/GuestProjectsPage.tsx b/src/frontend/src/pages/GuestProjectsPage/GuestProjectsPage.tsx new file mode 100644 index 0000000000..34be442798 --- /dev/null +++ b/src/frontend/src/pages/GuestProjectsPage/GuestProjectsPage.tsx @@ -0,0 +1,74 @@ +import { useAllProjects } from '../../hooks/projects.hooks'; +import LoadingIndicator from '../../components/LoadingIndicator'; +import ErrorPage from '../ErrorPage'; +import { Box, useMediaQuery } from '@mui/system'; +import { wbsPipe } from 'shared'; +import PageLayout from '../../components/PageLayout'; +import GuestProjectsCard from './GuestProjectsCard'; +import { useAllTeamTypes } from '../../hooks/team-types.hooks'; +import { Chip } from '@mui/material'; +import { useState } from 'react'; + +const GuestProjectsPage: React.FC = () => { + const { data: allProjects, isLoading, isError, error } = useAllProjects(); + const [selectedTeamTypes, setSelectedTeamTypes] = useState([]); + const isMobilePortrait = useMediaQuery('(max-width:480px)'); + const { + isLoading: teamTypesIsLoading, + isError: teamTypesIsError, + data: teamTypes, + error: teamTypesError + } = useAllTeamTypes(); + + if (isLoading || !allProjects || teamTypesIsLoading || !teamTypes) return ; + if (isError) return ; + if (teamTypesIsError) return ; + + const filteredProjects = allProjects.filter( + (project) => + selectedTeamTypes.length === 0 || project.teamTypes.some((t) => t !== null && selectedTeamTypes.includes(t.name)) + ); + + return ( + + + {teamTypes.map((team) => ( + + setSelectedTeamTypes((prev) => + prev?.includes(team.name) ? prev.filter((t: string) => t !== team.name) : [...(prev || []), team.name] + ) + } + clickable + color={selectedTeamTypes?.includes(team.name) ? 'primary' : 'default'} + /> + ))} + + + {filteredProjects.map((p) => ( + + ))} + + + ); +}; + +export default GuestProjectsPage; diff --git a/src/frontend/src/pages/ProjectsPage/Projects.tsx b/src/frontend/src/pages/ProjectsPage/Projects.tsx index 4fc4e330cf..4ba1c6d2e4 100644 --- a/src/frontend/src/pages/ProjectsPage/Projects.tsx +++ b/src/frontend/src/pages/ProjectsPage/Projects.tsx @@ -10,6 +10,7 @@ import ProjectsPage from './ProjectsPage'; import CreateWorkPackageForm from '../WorkPackageForm/CreateWorkPackageForm'; import ProjectCreateContainer from '../ProjectDetailPage/ProjectForm/ProjectCreateContainer'; import PartPage from '../PartPage/PartPage'; +import GuestProjectsPage from '../GuestProjectsPage/GuestProjectsPage'; const Projects: React.FC = () => { return ( @@ -19,6 +20,7 @@ const Projects: React.FC = () => { + diff --git a/src/frontend/src/utils/routes.ts b/src/frontend/src/utils/routes.ts index 5194281381..e18ad4f19d 100644 --- a/src/frontend/src/utils/routes.ts +++ b/src/frontend/src/utils/routes.ts @@ -36,6 +36,7 @@ const PROJECTS_BY_WBS = PROJECTS + `/:wbsNum`; const PROJECTS_NEW = PROJECTS + `/new`; const WORK_PACKAGE_NEW = PROJECTS + `/work-package/new`; const PROJECT_PART = PROJECTS_BY_WBS + '/part/:indexNum'; +const GUEST_PROJECTS = PROJECTS + '/guest'; /**************** Teams Section ****************/ const TEAMS = `/teams`; @@ -105,6 +106,7 @@ export const routes = { PROJECTS_NEW, WORK_PACKAGE_NEW, PROJECT_PART, + GUEST_PROJECTS, CHANGE_REQUESTS, ALL_CHANGE_REQUESTS, diff --git a/src/shared/src/types/project-types.ts b/src/shared/src/types/project-types.ts index fb1a22bad1..c4e73f2b2f 100644 --- a/src/shared/src/types/project-types.ts +++ b/src/shared/src/types/project-types.ts @@ -82,6 +82,7 @@ export interface ProjectPreview extends WbsElementPreview { abbreviation?: string; workPackages: WorkPackagePreview[]; teams: { teamName: string; teamId: string }[]; + teamTypes: TeamType[]; } export interface ProjectOverview extends ProjectPreview { From 2783870a76d09f9049083ee6f251e5b2132c581b Mon Sep 17 00:00:00 2001 From: Sarah Taylor <150694563+staysgt@users.noreply.github.com> Date: Sun, 8 Mar 2026 20:01:40 -0400 Subject: [PATCH 2/7] #4003 fix query arg/transformer --- .../src/prisma-query-args/projects.query-args.ts | 1 + src/backend/src/transformers/projects.transformer.ts | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/backend/src/prisma-query-args/projects.query-args.ts b/src/backend/src/prisma-query-args/projects.query-args.ts index a3bed1ef29..c01fd6083b 100644 --- a/src/backend/src/prisma-query-args/projects.query-args.ts +++ b/src/backend/src/prisma-query-args/projects.query-args.ts @@ -133,6 +133,7 @@ export const getProjectOverviewQueryArgs = (organizationId: string) => abbreviation: true, teams: { select: { + teamType: true, teamId: true, teamName: true } diff --git a/src/backend/src/transformers/projects.transformer.ts b/src/backend/src/transformers/projects.transformer.ts index 7719a04b03..7c95e7bb0e 100644 --- a/src/backend/src/transformers/projects.transformer.ts +++ b/src/backend/src/transformers/projects.transformer.ts @@ -117,7 +117,17 @@ export const projectPreviewTransformer = (project: Prisma.ProjectGetPayload team.teamType), + teamTypes: project.teams.flatMap((team) => + team.teamType + ? [ + { + ...team.teamType, + dateDeleted: team.teamType.dateDeleted ?? undefined, + deletedById: team.teamType.deletedById ?? undefined + } + ] + : [] + ), teams: project.teams, workPackages: project.workPackages.map((wp) => ({ ...wp, From 27817d0bc7bbf54b16caadb40d798bf3aa23d67c Mon Sep 17 00:00:00 2001 From: Sarah Taylor <150694563+staysgt@users.noreply.github.com> Date: Sun, 15 Mar 2026 19:36:06 -0400 Subject: [PATCH 3/7] #4003 changes from review --- .../prisma-query-args/projects.query-args.ts | 14 ++++++++++-- .../src/transformers/projects.transformer.ts | 22 ++++++++++--------- src/shared/src/types/project-types.ts | 2 +- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/backend/src/prisma-query-args/projects.query-args.ts b/src/backend/src/prisma-query-args/projects.query-args.ts index c01fd6083b..01bba4116b 100644 --- a/src/backend/src/prisma-query-args/projects.query-args.ts +++ b/src/backend/src/prisma-query-args/projects.query-args.ts @@ -100,7 +100,12 @@ export const getProjectPreviewQueryArgs = (organizationId: string) => abbreviation: true, teams: { select: { - teamType: true, + teamType: { + select: { + teamTypeId: true, + name: true + } + }, teamId: true, teamName: true } @@ -133,7 +138,12 @@ export const getProjectOverviewQueryArgs = (organizationId: string) => abbreviation: true, teams: { select: { - teamType: true, + teamType: { + select: { + teamTypeId: true, + name: true + } + }, teamId: true, teamName: true } diff --git a/src/backend/src/transformers/projects.transformer.ts b/src/backend/src/transformers/projects.transformer.ts index 7c95e7bb0e..7b068470f8 100644 --- a/src/backend/src/transformers/projects.transformer.ts +++ b/src/backend/src/transformers/projects.transformer.ts @@ -117,16 +117,18 @@ export const projectPreviewTransformer = (project: Prisma.ProjectGetPayload - team.teamType - ? [ - { - ...team.teamType, - dateDeleted: team.teamType.dateDeleted ?? undefined, - deletedById: team.teamType.deletedById ?? undefined - } - ] - : [] + teamTypes: Array.from( + project.teams + .reduce((acc, team) => { + if (team.teamType) { + acc.set(team.teamType.teamTypeId, { + name: team.teamType.name, + teamTypeId: team.teamType.teamTypeId + }); + } + return acc; + }, new Map()) + .values() ), teams: project.teams, workPackages: project.workPackages.map((wp) => ({ diff --git a/src/shared/src/types/project-types.ts b/src/shared/src/types/project-types.ts index c4e73f2b2f..96be30bf0c 100644 --- a/src/shared/src/types/project-types.ts +++ b/src/shared/src/types/project-types.ts @@ -82,7 +82,7 @@ export interface ProjectPreview extends WbsElementPreview { abbreviation?: string; workPackages: WorkPackagePreview[]; teams: { teamName: string; teamId: string }[]; - teamTypes: TeamType[]; + teamTypes: { name: string; teamTypeId: string }[]; } export interface ProjectOverview extends ProjectPreview { From 2d0ceae122bd7f9931d1fd309a1e221ed155d060 Mon Sep 17 00:00:00 2001 From: Raphael Bessin Date: Sun, 15 Mar 2026 16:38:38 -0400 Subject: [PATCH 4/7] #4049 fix: updated parser to handle windows line endings in frontmatter --- src/docs-site/scripts/sync-skills.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs-site/scripts/sync-skills.mjs b/src/docs-site/scripts/sync-skills.mjs index bc905547c0..c707431bbf 100644 --- a/src/docs-site/scripts/sync-skills.mjs +++ b/src/docs-site/scripts/sync-skills.mjs @@ -46,7 +46,7 @@ function parseFrontmatter(content) { const restOfContent = content.slice(match[0].length); // Parse YAML - const lines = yamlContent.split('\n'); + const lines = yamlContent.split(/\r?\n/); const metadata = {}; let currentKey = null; let currentValue = ''; From 06204d6ed5bfe5c26bb6997bf4a2c89cc4cdf27d Mon Sep 17 00:00:00 2001 From: Sarah Taylor <150694563+staysgt@users.noreply.github.com> Date: Mon, 16 Mar 2026 19:31:24 -0400 Subject: [PATCH 5/7] #4003 changes from review --- .../GuestProjectsPage/GuestProjectsCard.tsx | 38 ++++++++++--------- .../HomePage/components/FeaturedProjects.tsx | 3 +- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx b/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx index 35644f874a..38535d6cf7 100644 --- a/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx +++ b/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx @@ -42,7 +42,7 @@ const GuestProjectsCard: React.FC = ({ project }) => { > {wbsNamePipe(singleProject)} - {activeWorkPackages[0] ? ( + {activeWorkPackages[0]?.stage ? ( = ({ project }) => { bgcolor: alpha(theme.palette.primary.main, 0.45), color: theme.palette.primary.light }} - label={activeWorkPackages[0]?.stage} + label={activeWorkPackages[0].stage} /> ) : null} @@ -77,21 +77,25 @@ const GuestProjectsCard: React.FC = ({ project }) => { {singleProject.summary} - - - - Learn more - - - + + + Learn more + + ); diff --git a/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx b/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx index 1b25ed884d..fdbf472bf8 100644 --- a/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx +++ b/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx @@ -12,6 +12,7 @@ import ScrollablePageBlock from './ScrollablePageBlock'; import EmptyPageBlockDisplay from './EmptyPageBlockDisplay'; import { Box, Stack, useMediaQuery } from '@mui/material'; import { Error } from '@mui/icons-material'; +import GuestProjectsCard from '../../GuestProjectsPage/GuestProjectsCard'; const NoFeaturedProjectsDisplay: React.FC = () => { return ( @@ -51,7 +52,7 @@ const FeaturedProjects: React.FC = () => { {featuredProjects.length === 0 ? ( ) : ( - featuredProjects.map((p) => ) + featuredProjects.map((p) => ) )} From ee429761676ed68b786b0ba76885bb0a5665a589 Mon Sep 17 00:00:00 2001 From: Sarah Taylor <150694563+staysgt@users.noreply.github.com> Date: Mon, 16 Mar 2026 19:34:53 -0400 Subject: [PATCH 6/7] #4003 fix lint --- src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx b/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx index fdbf472bf8..ad655cc04b 100644 --- a/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx +++ b/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx @@ -3,10 +3,8 @@ * See the LICENSE file in the repository root folder for details. */ -import FeaturedProjectsCard from './FeaturedProjectsCard'; import { useFeaturedProjects } from '../../../hooks/organizations.hooks'; import ErrorPage from '../../ErrorPage'; -import { wbsPipe } from 'shared'; import LoadingIndicator from '../../../components/LoadingIndicator'; import ScrollablePageBlock from './ScrollablePageBlock'; import EmptyPageBlockDisplay from './EmptyPageBlockDisplay'; From cb5762b2e48e17c6d0eb1a281185944dd81fcb58 Mon Sep 17 00:00:00 2001 From: Sarah Taylor <150694563+staysgt@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:00:14 -0400 Subject: [PATCH 7/7] #4003 fix route, make boxes better --- .../GuestProjectsPage/GuestProjectsCard.tsx | 27 +++++++++++++------ .../GuestProjectsPage/GuestProjectsPage.tsx | 7 +++-- .../HomePage/components/FeaturedProjects.tsx | 6 ++++- .../src/pages/ProjectsPage/Projects.tsx | 2 -- .../src/pages/ProjectsPage/ProjectsPage.tsx | 4 +++ src/frontend/src/utils/routes.ts | 2 -- 6 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx b/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx index 38535d6cf7..dde9a82f3d 100644 --- a/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx +++ b/src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx @@ -1,4 +1,4 @@ -import { alpha, Box, Card, CardContent, Chip, Stack, Typography, useTheme, useMediaQuery, Link } from '@mui/material'; +import { alpha, Box, Card, CardContent, Chip, Stack, Typography, useTheme, Link } from '@mui/material'; import { wbsNamePipe, ProjectPreview, wbsPipe, WbsElementStatus } from 'shared'; import { datePipe } from '../../utils/pipes'; import { NERButton } from '../../components/NERButton'; @@ -13,7 +13,6 @@ interface ProjectCardProps { const GuestProjectsCard: React.FC = ({ project }) => { const theme = useTheme(); - const isMobilePortrait = useMediaQuery('(max-width:600px)'); const { data: singleProject, isLoading, isError, error } = useSingleProject(project.wbsNum); if (isLoading || !singleProject) return ; if (isError) return ; @@ -24,14 +23,15 @@ const GuestProjectsCard: React.FC = ({ project }) => { - + @@ -67,7 +67,7 @@ const GuestProjectsCard: React.FC = ({ project }) => { ? `${singleProject.manager.firstName} ${singleProject.manager.lastName}` : 'N/A'} - + {datePipe(singleProject.startDate) + ' ⟝ ' + singleProject.duration + @@ -76,7 +76,18 @@ const GuestProjectsCard: React.FC = ({ project }) => { - {singleProject.summary} + + {singleProject.summary} + { {teamTypes.map((team) => ( { } clickable color={selectedTeamTypes?.includes(team.name) ? 'primary' : 'default'} + sx={{ flexShrink: 0 }} /> ))} diff --git a/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx b/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx index ad655cc04b..8b930d5017 100644 --- a/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx +++ b/src/frontend/src/pages/HomePage/components/FeaturedProjects.tsx @@ -50,7 +50,11 @@ const FeaturedProjects: React.FC = () => { {featuredProjects.length === 0 ? ( ) : ( - featuredProjects.map((p) => ) + featuredProjects.map((p) => ( + + + + )) )} diff --git a/src/frontend/src/pages/ProjectsPage/Projects.tsx b/src/frontend/src/pages/ProjectsPage/Projects.tsx index 4ba1c6d2e4..4fc4e330cf 100644 --- a/src/frontend/src/pages/ProjectsPage/Projects.tsx +++ b/src/frontend/src/pages/ProjectsPage/Projects.tsx @@ -10,7 +10,6 @@ import ProjectsPage from './ProjectsPage'; import CreateWorkPackageForm from '../WorkPackageForm/CreateWorkPackageForm'; import ProjectCreateContainer from '../ProjectDetailPage/ProjectForm/ProjectCreateContainer'; import PartPage from '../PartPage/PartPage'; -import GuestProjectsPage from '../GuestProjectsPage/GuestProjectsPage'; const Projects: React.FC = () => { return ( @@ -20,7 +19,6 @@ const Projects: React.FC = () => { - diff --git a/src/frontend/src/pages/ProjectsPage/ProjectsPage.tsx b/src/frontend/src/pages/ProjectsPage/ProjectsPage.tsx index c50e69e35f..0d35d580dc 100644 --- a/src/frontend/src/pages/ProjectsPage/ProjectsPage.tsx +++ b/src/frontend/src/pages/ProjectsPage/ProjectsPage.tsx @@ -14,6 +14,7 @@ import { useCurrentUser } from '../../hooks/users.hooks'; import { isGuest } from 'shared'; import { Add } from '@mui/icons-material'; import { useHistory } from 'react-router-dom'; +import GuestProjectsPage from '../GuestProjectsPage/GuestProjectsPage'; /** * Cards of all projects that this user is in their team. @@ -24,6 +25,9 @@ const ProjectsPage: React.FC = () => { const user = useCurrentUser(); const history = useHistory(); + if (isGuest(user.role)) { + return ; + } return (