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[];