diff --git a/src/backend/src/controllers/teams.controllers.ts b/src/backend/src/controllers/teams.controllers.ts index 1d91f7bee0..388db3c83e 100644 --- a/src/backend/src/controllers/teams.controllers.ts +++ b/src/backend/src/controllers/teams.controllers.ts @@ -152,7 +152,7 @@ export default class TeamsController { static async completeOnboarding(req: Request, res: Response, next: NextFunction) { try { - await TeamsService.completeOnboarding(req.currentUser, req.organization); + await TeamsService.completeOnboarding(req.currentUser); res.status(200).json({ message: 'Successfully completed onboarding' }); } catch (error: unknown) { diff --git a/src/backend/src/prisma-query-args/auth-user.query-args.ts b/src/backend/src/prisma-query-args/auth-user.query-args.ts index 1f1c1e4dbe..3b5be4caed 100644 --- a/src/backend/src/prisma-query-args/auth-user.query-args.ts +++ b/src/backend/src/prisma-query-args/auth-user.query-args.ts @@ -48,6 +48,11 @@ export const getAuthUserQueryArgs = (organizationId: string) => where: { organizationId } + }, + onboardedTeamTypes: { + where: { + organizationId + } } } }); diff --git a/src/backend/src/prisma/migrations/20250122032518_added_users_onboarded_team_types/migration.sql b/src/backend/src/prisma/migrations/20250122032518_added_users_onboarded_team_types/migration.sql new file mode 100644 index 0000000000..bcce0c1cb1 --- /dev/null +++ b/src/backend/src/prisma/migrations/20250122032518_added_users_onboarded_team_types/migration.sql @@ -0,0 +1,17 @@ +-- CreateTable +CREATE TABLE "_onboardedTeamTypes" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "_onboardedTeamTypes_AB_unique" ON "_onboardedTeamTypes"("A", "B"); + +-- CreateIndex +CREATE INDEX "_onboardedTeamTypes_B_index" ON "_onboardedTeamTypes"("B"); + +-- AddForeignKey +ALTER TABLE "_onboardedTeamTypes" ADD CONSTRAINT "_onboardedTeamTypes_A_fkey" FOREIGN KEY ("A") REFERENCES "Team_Type"("teamTypeId") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_onboardedTeamTypes" ADD CONSTRAINT "_onboardedTeamTypes_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("userId") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/backend/src/prisma/schema.prisma b/src/backend/src/prisma/schema.prisma index 8a7412a5b3..7dbb5f6751 100644 --- a/src/backend/src/prisma/schema.prisma +++ b/src/backend/src/prisma/schema.prisma @@ -235,6 +235,7 @@ model User { checkedChecklists Checklist[] @relation(name: "checkedChecklists") contacts Contact[] onboardingTeamTypes Team_Type[] @relation(name: "onboardingTeamTypes") + onboardedTeamTypes Team_Type[] @relation(name: "onboardedTeamTypes") } model Role { @@ -774,6 +775,7 @@ model Team_Type { calendarId String? checklists Checklist[] usersOnboarding User[] @relation(name: "onboardingTeamTypes") + usersOnboarded User[] @relation(name: "onboardedTeamTypes") dateDeleted DateTime? deletedById String? diff --git a/src/backend/src/services/teams.services.ts b/src/backend/src/services/teams.services.ts index f668880c68..11fd3168c2 100644 --- a/src/backend/src/services/teams.services.ts +++ b/src/backend/src/services/teams.services.ts @@ -1,4 +1,4 @@ -import { isAdmin, isHead, RoleEnum, Team, TeamType } from 'shared'; +import { isAdmin, isHead, Team, TeamType } from 'shared'; import { Organization, User, WBS_Element_Status } from '@prisma/client'; import prisma from '../prisma/prisma'; import teamTransformer from '../transformers/teams.transformer'; @@ -609,30 +609,25 @@ export default class TeamsService { return teamTypeTransformer(updatedTeamType); } - static async completeOnboarding(submitter: User, organization: Organization) { - // remove the user from any onboardingTeamTypes they are a part of - const user = await prisma.user.update({ + static async completeOnboarding(submitter: User) { + const onboardingTeamTypes = await prisma.team_Type.findMany({ + where: { usersOnboarding: { some: { userId: submitter.userId } } } + }); + + const teamTypeIds = onboardingTeamTypes.map((teamType) => ({ teamTypeId: teamType.teamTypeId })); + + // remove the user from any onboardingTeamTypes they are a part of and add them to the onboardedTeamTypes + await prisma.user.update({ where: { userId: submitter.userId }, - include: { roles: true }, data: { + onboardedTeamTypes: { + set: teamTypeIds + }, onboardingTeamTypes: { set: [] } } }); - - // update the users role to member after they complete their onboarding - const currentRole = user.roles.find((role) => role.organizationId === organization.organizationId); - if (currentRole && currentRole.roleType !== RoleEnum.MEMBER) { - await prisma.role.update({ - where: { - uniqueRole: { userId: user.userId, organizationId: organization.organizationId } - }, - data: { - roleType: RoleEnum.MEMBER - } - }); - } } static async setTeamTypeImage( diff --git a/src/backend/src/services/users.services.ts b/src/backend/src/services/users.services.ts index b867cc1cd0..f82b087111 100644 --- a/src/backend/src/services/users.services.ts +++ b/src/backend/src/services/users.services.ts @@ -303,7 +303,8 @@ export default class UsersService { teamsAsLead: [], teamsAsMember: [], roles: [], - onboardingTeamTypes: [] + onboardingTeamTypes: [], + onboardedTeamTypes: [] }), token }; @@ -356,7 +357,8 @@ export default class UsersService { teamsAsLead: [], teamsAsMember: [], roles: [], - onboardingTeamTypes: [] + onboardingTeamTypes: [], + onboardedTeamTypes: [] }); } diff --git a/src/backend/src/transformers/auth-user.transformer.ts b/src/backend/src/transformers/auth-user.transformer.ts index ec3c104bee..3d2e4ad8f0 100644 --- a/src/backend/src/transformers/auth-user.transformer.ts +++ b/src/backend/src/transformers/auth-user.transformer.ts @@ -31,6 +31,7 @@ const authenticatedUserTransformer = ( organizations: user.organizations.map((organization) => organization.organizationId), currentOrganization: currentOrganization ? organizationTransformer(currentOrganization) : undefined, onboardingTeamTypeIds: user.onboardingTeamTypes.map((teamType) => teamType.teamTypeId), + onboardedTeamTypeIds: user.onboardedTeamTypes.map((teamType) => teamType.teamTypeId), teamsAsHead: user.teamsAsHead.map(teamTransformer), teamsAsLead: user.teamsAsLead.map(teamTransformer), permissions: user.roles diff --git a/src/frontend/src/app/AppMain.tsx b/src/frontend/src/app/AppMain.tsx index 6552fb7858..77e111ea9b 100644 --- a/src/frontend/src/app/AppMain.tsx +++ b/src/frontend/src/app/AppMain.tsx @@ -15,7 +15,7 @@ const AppMain: React.FC = () => { - + diff --git a/src/frontend/src/hooks/onboarding.hook.ts b/src/frontend/src/hooks/onboarding.hook.ts index 95691029e5..9effd1aa25 100644 --- a/src/frontend/src/hooks/onboarding.hook.ts +++ b/src/frontend/src/hooks/onboarding.hook.ts @@ -160,7 +160,12 @@ export const useGetImageUrls = (imageList: { objectId: string; imageFileId: stri export const useChecklistProgress = (parentChecklists: Checklist[], checkedChecklists: Checklist[]) => { const [progress, setProgress] = useState(0); useEffect(() => { - if (!checkedChecklists || parentChecklists.length === 0) return; + if (parentChecklists.length === 0) { + setProgress(100); + return; + } + + if (!checkedChecklists) return; const totalChecklistsLength = parentChecklists.length; diff --git a/src/frontend/src/pages/HomePage/GuestHomePage.tsx b/src/frontend/src/pages/HomePage/GuestHomePage.tsx index 297559c47a..ac1da46919 100644 --- a/src/frontend/src/pages/HomePage/GuestHomePage.tsx +++ b/src/frontend/src/pages/HomePage/GuestHomePage.tsx @@ -13,6 +13,8 @@ import MemberEncouragement from './components/MemberEncouragement'; import GuestOrganizationInfo from './components/GuestOrganizationInfo'; import FeaturedProjects from './components/FeaturedProjects'; import OrganizationLogo from './components/OrganizationLogo'; +import NERModal from '../../components/NERModal'; +import { useEffect, useState } from 'react'; interface GuestHomePageProps { user: AuthenticatedUser; @@ -20,6 +22,16 @@ interface GuestHomePageProps { const GuestHomePage = ({ user }: GuestHomePageProps) => { const { isLoading, isError, error, data: userSettingsData } = useSingleUserSettings(user.userId); + const [showModal, setShowModal] = useState(false); + + // shows modal only once per session + useEffect(() => { + const hasSeenModal = sessionStorage.getItem('hasSeenModal'); + if (!hasSeenModal) { + setShowModal(true); + sessionStorage.setItem('hasSeenModal', 'true'); + } + }, []); if (isLoading || !userSettingsData) return ; if (isError) return ; @@ -57,6 +69,15 @@ const GuestHomePage = ({ user }: GuestHomePageProps) => { + setShowModal(false)} + showCloseButton + hideFormButtons + > + Ask your head to upgrade you to a member to gain full access + ); }; diff --git a/src/frontend/src/pages/HomePage/Home.tsx b/src/frontend/src/pages/HomePage/Home.tsx index 97309f4b19..d340aae218 100644 --- a/src/frontend/src/pages/HomePage/Home.tsx +++ b/src/frontend/src/pages/HomePage/Home.tsx @@ -9,23 +9,25 @@ import OnboardingHomePage from './OnboardingHomePage'; import SelectSubteamPage from './SelectSubteamPage'; import AcceptedPage from '../AcceptedPage/AcceptedPage'; import HomePage from './HomePage'; -import { isGuest } from 'shared'; import { useCurrentUser } from '../../hooks/users.hooks'; import IntroGuestHomePage from './IntroGuestHomePage'; +import { isGuest } from 'shared'; const Home: React.FC = () => { const user = useCurrentUser(); const onOnboarding = user.onboardingTeamTypeIds.length > 0; - const userRole = user.role; + const completedOnboarding = user.onboardedTeamTypeIds.length > 0; return ( - {!isGuest(userRole) && + {completedOnboarding && [routes.HOME_GUEST, routes.HOME_PNM, routes.HOME_ONBOARDING, routes.HOME_ACCEPT].map((path) => ( ))} - {!onOnboarding && isGuest(userRole) && } + {!onOnboarding && !completedOnboarding && isGuest(user.role) && ( + + )} {onOnboarding && } diff --git a/src/frontend/src/pages/HomePage/components/ChecklistSection.tsx b/src/frontend/src/pages/HomePage/components/ChecklistSection.tsx index 1febc34458..f98ee4f6a4 100644 --- a/src/frontend/src/pages/HomePage/components/ChecklistSection.tsx +++ b/src/frontend/src/pages/HomePage/components/ChecklistSection.tsx @@ -1,4 +1,4 @@ -import { Box, Grid } from '@mui/material'; +import { Box, Grid, Typography } from '@mui/material'; import { groupChecklists } from '../../../utils/onboarding.utils'; import Checklist from './Checklist'; import { Checklist as ChecklistType } from 'shared'; @@ -20,6 +20,19 @@ const ChecklistSection: React.FC = ({ usersChecklists, ch ))} + {!usersChecklists.length && ( + + No checklists found + + )} ); }; diff --git a/src/frontend/src/tests/test-support/test-data/authenticated-user.stub.ts b/src/frontend/src/tests/test-support/test-data/authenticated-user.stub.ts index afaf95acd2..a030f1a8f9 100644 --- a/src/frontend/src/tests/test-support/test-data/authenticated-user.stub.ts +++ b/src/frontend/src/tests/test-support/test-data/authenticated-user.stub.ts @@ -16,5 +16,6 @@ export const exampleAuthenticatedAdminUser: AuthenticatedUser = { changeRequestsToReviewId: [], organizations: [], onboardingTeamTypeIds: [], + onboardedTeamTypeIds: [], permissions: [] }; diff --git a/src/frontend/src/tests/test-support/test-data/users.stub.ts b/src/frontend/src/tests/test-support/test-data/users.stub.ts index 6cfc79e779..ec0e57cd46 100644 --- a/src/frontend/src/tests/test-support/test-data/users.stub.ts +++ b/src/frontend/src/tests/test-support/test-data/users.stub.ts @@ -26,6 +26,7 @@ export const exampleAdminUser: AuthenticatedUser = { changeRequestsToReviewId: [], organizations: ['yello'], onboardingTeamTypeIds: [], + onboardedTeamTypeIds: [], permissions: [] }; @@ -40,6 +41,7 @@ export const exampleAdminUser2: AuthenticatedUser = { changeRequestsToReviewId: [], organizations: [], onboardingTeamTypeIds: [], + onboardedTeamTypeIds: [], permissions: [] }; @@ -84,6 +86,7 @@ export const exampleMemberUser: AuthenticatedUser = { changeRequestsToReviewId: [], organizations: [], onboardingTeamTypeIds: [], + onboardedTeamTypeIds: [], permissions: [] }; @@ -98,6 +101,7 @@ export const exampleGuestUser: AuthenticatedUser = { changeRequestsToReviewId: [], organizations: [], onboardingTeamTypeIds: [], + onboardedTeamTypeIds: [], permissions: [] }; diff --git a/src/shared/src/types/user-types.ts b/src/shared/src/types/user-types.ts index 8c8be9062f..1a791525b1 100644 --- a/src/shared/src/types/user-types.ts +++ b/src/shared/src/types/user-types.ts @@ -84,6 +84,7 @@ export interface AuthenticatedUser { organizations: string[]; currentOrganization?: OrganizationPreview; onboardingTeamTypeIds: string[]; + onboardedTeamTypeIds: string[]; teamsAsHead?: Team[]; teamsAsLead?: Team[]; permissions: Permission[];