From c6f4ab7f91280a00513cb92b6ead60b8628567b7 Mon Sep 17 00:00:00 2001 From: harish Date: Sun, 4 Aug 2024 11:52:01 -0400 Subject: [PATCH 01/40] #2725 hook --- src/frontend/src/apis/organization.api.ts | 8 +++++ src/frontend/src/hooks/organization.hooks.ts | 18 ++++++++++ .../pages/AdminToolsPage/AdminToolsPage.tsx | 14 ++++++++ .../AdminToolsPage/AdminToolsRecruitment.tsx | 36 +++++++++++++++++++ src/frontend/src/utils/urls.ts | 2 ++ 5 files changed, 78 insertions(+) create mode 100644 src/frontend/src/apis/organization.api.ts create mode 100644 src/frontend/src/pages/AdminToolsPage/AdminToolsRecruitment.tsx diff --git a/src/frontend/src/apis/organization.api.ts b/src/frontend/src/apis/organization.api.ts new file mode 100644 index 0000000000..e6f850f063 --- /dev/null +++ b/src/frontend/src/apis/organization.api.ts @@ -0,0 +1,8 @@ +import axios from "axios"; +import { apiUrls } from "../utils/urls"; + +export const setOrganizationImages = (images: Express.Multer.File[]) => { + return axios.post<{ message: string }>(apiUrls.organizationsSetImages(), { + images + }); + }; \ No newline at end of file diff --git a/src/frontend/src/hooks/organization.hooks.ts b/src/frontend/src/hooks/organization.hooks.ts index 0f3616d042..c6dea7b0be 100644 --- a/src/frontend/src/hooks/organization.hooks.ts +++ b/src/frontend/src/hooks/organization.hooks.ts @@ -1,5 +1,7 @@ import { useContext, useState } from 'react'; import { OrganizationContext } from '../app/AppOrganizationContext'; +import { MutationFunction, useMutation, useQuery, useQueryClient } from 'react-query'; +import { setOrganizationImages } from '../apis/organization.api'; interface OrganizationProvider { organizationId: string; @@ -20,6 +22,22 @@ export const useProvideOrganization = (): OrganizationProvider => { }; }; +export const useSetOrganizationImages = () => { + const queryClient = useQueryClient(); + + return useMutation( + async (images: Express.Multer.File[]) => { + const { data } = await setOrganizationImages(images); + return data; + }, + { + onSuccess: () => { + queryClient.invalidateQueries(['organization images']); + }, + } + ); +}; + // Hook for child components to get the auth object export const useOrganization = () => { const context = useContext(OrganizationContext); diff --git a/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx b/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx index 57686c2f38..9c80467a7e 100644 --- a/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx +++ b/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx @@ -39,6 +39,7 @@ const AdminToolsPage: React.FC = () => { } if (isUserAdmin) { tabs.push({ tabUrlValue: 'miscellaneous', tabName: 'Miscellaneous' }); + tabs.push({ tabUrlValue: 'recruitment', tabName: 'Recruitment' }); } const UserManagementTab = () => { @@ -63,6 +64,16 @@ const AdminToolsPage: React.FC = () => { ) : ( ); + }; + + const RecruitmentTab = () => { + return isUserAdmin ? ( + <> + + + ) : ( + + ); }; return ( @@ -87,6 +98,9 @@ const AdminToolsPage: React.FC = () => { ) : tabIndex === 2 ? ( + ) : + tabIndex === 3 ? ( + ) : ( diff --git a/src/frontend/src/pages/AdminToolsPage/AdminToolsRecruitment.tsx b/src/frontend/src/pages/AdminToolsPage/AdminToolsRecruitment.tsx new file mode 100644 index 0000000000..39286b6bc9 --- /dev/null +++ b/src/frontend/src/pages/AdminToolsPage/AdminToolsRecruitment.tsx @@ -0,0 +1,36 @@ +import { Box } from '@mui/system'; +import { Typography } from '@mui/material'; +import WorkPackageTemplateTable from './ProjectsConfig/WorkPackageTemplateTable'; +import LinkTypeTable from './ProjectsConfig/LinkTypes/LinkTypeTable'; +import DescriptionBulletTypeTable from './ProjectsConfig/DescriptionBulletTypes/DescriptionBulletTypeTable'; +import CarsTable from './ProjectsConfig/CarsTable'; +import UsefulLinksTable from './ProjectsConfig/UsefulLinks/UsefulLinksTable'; + +const AdminToolsProjectsConfig: React.FC = () => { + return ( + + + Cars Config + + + + Links Config + + + + Useful Links + + + + Description Bullet Types + + + + Work Package Templates + + + + ); +}; + +export default AdminToolsProjectsConfig; diff --git a/src/frontend/src/utils/urls.ts b/src/frontend/src/utils/urls.ts index 6df1e56324..ae0792e0ec 100644 --- a/src/frontend/src/utils/urls.ts +++ b/src/frontend/src/utils/urls.ts @@ -169,6 +169,7 @@ const workPackageTemplateDelete = (workPackageTemplateId: string) => const organizations = () => `${API_URL}/organizations`; const organizationsUsefulLinks = () => `${organizations()}/useful-links`; const organizationsSetUsefulLinks = () => `${organizationsUsefulLinks()}/set`; +const organizationsSetImages = () => `${organizations()}/images/update`; /******************* Car Endpoints ********************/ const cars = () => `${API_URL}/cars`; @@ -309,6 +310,7 @@ export const apiUrls = { organizationsUsefulLinks, organizationsSetUsefulLinks, + organizationsSetImages, cars, carsCreate, From e1fecd8e687d6bf22d9fcc64c2608c037d0bcf24 Mon Sep 17 00:00:00 2001 From: harish Date: Mon, 5 Aug 2024 21:50:19 -0400 Subject: [PATCH 02/40] #2725 tabs --- .../src/pages/AdminToolsPage/AdminToolsPage.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx b/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx index 9c80467a7e..4f5edeaca2 100644 --- a/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx +++ b/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx @@ -17,6 +17,7 @@ import { useState } from 'react'; import NERTabs from '../../components/Tabs'; import { routes } from '../../utils/routes'; import { Box } from '@mui/system'; +import AdminToolsRecruitment from './AdminToolsRecruitment'; const AdminToolsPage: React.FC = () => { const currentUser = useCurrentUser(); @@ -30,6 +31,8 @@ const AdminToolsPage: React.FC = () => { const tabs = []; + + if (isUserHead || isUserAdmin) { tabs.push({ tabUrlValue: 'user-management', tabName: 'User Management' }); tabs.push({ tabUrlValue: 'project-configuration', tabName: 'Project Configuration' }); @@ -38,9 +41,10 @@ const AdminToolsPage: React.FC = () => { tabs.push({ tabUrlValue: 'finance-configuration', tabName: 'Finance Configuration' }); } if (isUserAdmin) { - tabs.push({ tabUrlValue: 'miscellaneous', tabName: 'Miscellaneous' }); tabs.push({ tabUrlValue: 'recruitment', tabName: 'Recruitment' }); + tabs.push({ tabUrlValue: 'miscellaneous', tabName: 'Miscellaneous' }); } + const UserManagementTab = () => { return isUserAdmin ? ( @@ -69,12 +73,15 @@ const AdminToolsPage: React.FC = () => { const RecruitmentTab = () => { return isUserAdmin ? ( <> - + ) : ( - + +

You do not have access to this tab.

+
); }; + return ( Date: Thu, 15 Aug 2024 19:03:04 -0400 Subject: [PATCH 03/40] 2725 revert --- .../pages/AdminToolsPage/AdminToolsPage.tsx | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx b/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx index 4f5edeaca2..9cdf7d1ad1 100644 --- a/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx +++ b/src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx @@ -17,7 +17,7 @@ import { useState } from 'react'; import NERTabs from '../../components/Tabs'; import { routes } from '../../utils/routes'; import { Box } from '@mui/system'; -import AdminToolsRecruitment from './AdminToolsRecruitment'; +import AdminToolsRecruitmentConfig from './RecruitmentConfig/AdminToolsRecruitmentConfig'; const AdminToolsPage: React.FC = () => { const currentUser = useCurrentUser(); @@ -31,8 +31,6 @@ const AdminToolsPage: React.FC = () => { const tabs = []; - - if (isUserHead || isUserAdmin) { tabs.push({ tabUrlValue: 'user-management', tabName: 'User Management' }); tabs.push({ tabUrlValue: 'project-configuration', tabName: 'Project Configuration' }); @@ -44,7 +42,6 @@ const AdminToolsPage: React.FC = () => { tabs.push({ tabUrlValue: 'recruitment', tabName: 'Recruitment' }); tabs.push({ tabUrlValue: 'miscellaneous', tabName: 'Miscellaneous' }); } - const UserManagementTab = () => { return isUserAdmin ? ( @@ -68,20 +65,7 @@ const AdminToolsPage: React.FC = () => { ) : ( ); - }; - - const RecruitmentTab = () => { - return isUserAdmin ? ( - <> - - - ) : ( - -

You do not have access to this tab.

-
- ); }; - return ( { ) : tabIndex === 2 ? ( - ) : - tabIndex === 3 ? ( - + ) : tabIndex === 3 ? ( + ) : ( From 654d22b26cfb8b432950d64778937a09104cec9e Mon Sep 17 00:00:00 2001 From: harish Date: Tue, 3 Sep 2024 18:55:54 -0400 Subject: [PATCH 04/40] #2725 upload button appearing --- src/frontend/src/apis/organization.api.ts | 2 +- src/frontend/src/hooks/organization.hooks.ts | 7 +- .../AdminToolsRecruitmentConfig.tsx | 88 ++++++++++++++++++- 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/src/frontend/src/apis/organization.api.ts b/src/frontend/src/apis/organization.api.ts index e6f850f063..bc9a0a8fd8 100644 --- a/src/frontend/src/apis/organization.api.ts +++ b/src/frontend/src/apis/organization.api.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { apiUrls } from "../utils/urls"; -export const setOrganizationImages = (images: Express.Multer.File[]) => { +export const setOrganizationImages = (images: File[]) => { return axios.post<{ message: string }>(apiUrls.organizationsSetImages(), { images }); diff --git a/src/frontend/src/hooks/organization.hooks.ts b/src/frontend/src/hooks/organization.hooks.ts index c6dea7b0be..0eeb986030 100644 --- a/src/frontend/src/hooks/organization.hooks.ts +++ b/src/frontend/src/hooks/organization.hooks.ts @@ -25,9 +25,9 @@ export const useProvideOrganization = (): OrganizationProvider => { export const useSetOrganizationImages = () => { const queryClient = useQueryClient(); - return useMutation( - async (images: Express.Multer.File[]) => { - const { data } = await setOrganizationImages(images); + return useMutation( + async (images: File[]) => { + const { data } = await setOrganizationImages(images); return data; }, { @@ -38,6 +38,7 @@ export const useSetOrganizationImages = () => { ); }; + // Hook for child components to get the auth object export const useOrganization = () => { const context = useContext(OrganizationContext); diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index d07889e79f..85bbbf0164 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -1,11 +1,42 @@ -import { Box, Grid, Typography } from '@mui/material'; +import { Box, Grid, Typography, Button, CircularProgress } from '@mui/material'; import MilestoneTable from './MilestoneTable'; import FAQsTable from './FAQTable'; +import { useSetOrganizationImages } from '../../../hooks/organization.hooks'; +import { useState } from 'react'; +import FileUploadIcon from '@mui/icons-material/FileUpload'; +import { useToast } from '../../../hooks/toasts.hooks'; const AdminToolsRecruitmentConfig: React.FC = () => { + const { isLoading: organizationImagesIsLoading, mutateAsync: organizationImages } = useSetOrganizationImages(); + const [addedImages, setAddedImages] = useState<{ exploreAsGuest: File[], applyInterest: File[] }>({ exploreAsGuest: [], applyInterest: [] }); + const toast = useToast(); + + const handleFileUpload = async (files: File[], type: 'exploreAsGuest' | 'applyInterest') => { + const validFiles: File[] = []; + files.forEach((file) => { + if (file.size < 1000000) { + validFiles.push(file); + setAddedImages((prevImages) => ({ + ...prevImages, + [type]: [...prevImages[type], file], + })); + } else { + toast.error(`Error uploading ${file.name}; file must be less than 1 MB`, 5000); + } + }); + + if (validFiles.length > 0) { + try { + await organizationImages(validFiles); // Upload valid files + } catch (error) { + console.error('Error uploading images:', error); + } + } + }; + return ( - + Recruitment Config @@ -16,6 +47,59 @@ const AdminToolsRecruitmentConfig: React.FC = () => { + + Recruitment Images + + + + + {organizationImagesIsLoading && } + + + + + {organizationImagesIsLoading && } + ); }; From 4b7de2f0bf243283337bbf5a8519cd457a24bc7b Mon Sep 17 00:00:00 2001 From: harish Date: Fri, 6 Sep 2024 17:25:56 -0400 Subject: [PATCH 05/40] #2725 fixing hook --- src/frontend/src/apis/organization.api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/apis/organization.api.ts b/src/frontend/src/apis/organization.api.ts index bc9a0a8fd8..07e89b753e 100644 --- a/src/frontend/src/apis/organization.api.ts +++ b/src/frontend/src/apis/organization.api.ts @@ -3,6 +3,7 @@ import { apiUrls } from "../utils/urls"; export const setOrganizationImages = (images: File[]) => { return axios.post<{ message: string }>(apiUrls.organizationsSetImages(), { - images + applyInterestImageId: images[0], + exploreAsGuestImageId: images[1] }); }; \ No newline at end of file From 6192c7992b85ae6d367e76dffbee14f431987fb9 Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 11 Sep 2024 18:20:37 -0400 Subject: [PATCH 06/40] #2725 handling nulls in endpoint --- .../src/services/organizations.services.ts | 26 +++++++++++++------ src/frontend/src/apis/organization.api.ts | 17 +++++++----- .../AdminToolsRecruitmentConfig.tsx | 11 ++++---- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/src/services/organizations.services.ts b/src/backend/src/services/organizations.services.ts index 45c917fd7a..5f80fc92d2 100644 --- a/src/backend/src/services/organizations.services.ts +++ b/src/backend/src/services/organizations.services.ts @@ -64,25 +64,35 @@ export default class OrganizationsService { * @param images the images which are being set */ static async setImages( - applyInterestImage: Express.Multer.File, - exploreAsGuestImage: Express.Multer.File, + applyInterestImage: Express.Multer.File | null, + exploreAsGuestImage: Express.Multer.File | null, submitter: User, organizationId: string ) { - if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) + if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) { throw new AccessDeniedAdminOnlyException('update images'); + } - const applyInterestImageData = uploadFile(applyInterestImage); - const exploreAsGuestImageData = uploadFile(exploreAsGuestImage); + let applyInterestImageId: string | undefined = undefined; + let exploreAsGuestImageId: string | undefined = undefined; + + if (applyInterestImage) { + const applyInterestImageData = await uploadFile(applyInterestImage); + applyInterestImageId = applyInterestImageData.id; + } + + if (exploreAsGuestImage) { + const exploreAsGuestImageData = await uploadFile(exploreAsGuestImage); + exploreAsGuestImageId = exploreAsGuestImageData.id; + } const newImages = await prisma.organization.update({ where: { organizationId }, data: { - applyInterestImageId: (await applyInterestImageData).id, - exploreAsGuestImageId: (await exploreAsGuestImageData).id + ...(applyInterestImageId ? { applyInterestImageId } : {}), + ...(exploreAsGuestImageId ? { exploreAsGuestImageId } : {}) } }); - return newImages; } diff --git a/src/frontend/src/apis/organization.api.ts b/src/frontend/src/apis/organization.api.ts index 07e89b753e..21a831af67 100644 --- a/src/frontend/src/apis/organization.api.ts +++ b/src/frontend/src/apis/organization.api.ts @@ -1,9 +1,12 @@ -import axios from "axios"; -import { apiUrls } from "../utils/urls"; +import axios from '../utils/axios'; +import { apiUrls } from '../utils/urls'; export const setOrganizationImages = (images: File[]) => { - return axios.post<{ message: string }>(apiUrls.organizationsSetImages(), { - applyInterestImageId: images[0], - exploreAsGuestImageId: images[1] - }); - }; \ No newline at end of file + const formData = new FormData(); + + formData.append('applyInterestImage', images[0]); + formData.append('exploreAsGuestImage', images[1]); + + return axios.post<{ message: string }>(apiUrls.organizationsSetImages(), formData, { + }); +}; diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index 85bbbf0164..59aed603f4 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -8,7 +8,10 @@ import { useToast } from '../../../hooks/toasts.hooks'; const AdminToolsRecruitmentConfig: React.FC = () => { const { isLoading: organizationImagesIsLoading, mutateAsync: organizationImages } = useSetOrganizationImages(); - const [addedImages, setAddedImages] = useState<{ exploreAsGuest: File[], applyInterest: File[] }>({ exploreAsGuest: [], applyInterest: [] }); + const [addedImages, setAddedImages] = useState<{ exploreAsGuest: File[]; applyInterest: File[] }>({ + exploreAsGuest: [], + applyInterest: [] + }); const toast = useToast(); const handleFileUpload = async (files: File[], type: 'exploreAsGuest' | 'applyInterest') => { @@ -18,7 +21,7 @@ const AdminToolsRecruitmentConfig: React.FC = () => { validFiles.push(file); setAddedImages((prevImages) => ({ ...prevImages, - [type]: [...prevImages[type], file], + [type]: [...prevImages[type], file] })); } else { toast.error(`Error uploading ${file.name}; file must be less than 1 MB`, 5000); @@ -27,7 +30,7 @@ const AdminToolsRecruitmentConfig: React.FC = () => { if (validFiles.length > 0) { try { - await organizationImages(validFiles); // Upload valid files + await organizationImages(validFiles); } catch (error) { console.error('Error uploading images:', error); } @@ -63,7 +66,6 @@ const AdminToolsRecruitmentConfig: React.FC = () => { { if (e.target.files) { @@ -88,7 +90,6 @@ const AdminToolsRecruitmentConfig: React.FC = () => { { if (e.target.files) { From a2b08e74446d716f5f0b44fd62411cd42624004f Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 2 Oct 2024 16:26:09 -0400 Subject: [PATCH 07/40] #2725 prettier and linting --- src/frontend/src/hooks/organizations.hooks.ts | 2 +- .../RecruitmentConfig/AdminToolsRecruitmentConfig.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/hooks/organizations.hooks.ts b/src/frontend/src/hooks/organizations.hooks.ts index 5e0b71842e..a28b8dc43b 100644 --- a/src/frontend/src/hooks/organizations.hooks.ts +++ b/src/frontend/src/hooks/organizations.hooks.ts @@ -41,7 +41,7 @@ export const useSetOrganizationImages = () => { }, { onSuccess: () => { - queryClient.invalidateQueries(['organization images']); + queryClient.invalidateQueries(['organizations']); }, } ); diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index c16ad16ef4..45a8ff18d4 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -47,7 +47,7 @@ const AdminToolsRecruitmentConfig: React.FC = () => { }; fetchImages(); - }, [currentImages]); + }, [currentImages || imageUrls]); if (organizationIsLoading) return ; if (organizationIsError) return ; @@ -56,9 +56,9 @@ const AdminToolsRecruitmentConfig: React.FC = () => { const validFiles: File[] = []; files.forEach((file) => { if (file.size < 1000000) { - if (type == 'applyInterest') { + if (type === 'applyInterest') { validFiles[0] = file; - } else if (type == 'exploreAsGuest') { + } else if (type === 'exploreAsGuest') { validFiles[1] = file; } } else { From c39d96ad052546aec326938db2cacd0cd832c732 Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 2 Oct 2024 16:27:07 -0400 Subject: [PATCH 08/40] #2725 prettier --- src/backend/src/services/organizations.services.ts | 11 +++++------ src/backend/src/utils/google-integration.utils.ts | 2 +- src/frontend/src/apis/organization.api.ts | 3 +-- src/frontend/src/hooks/organizations.hooks.ts | 3 +-- src/shared/src/types/user-types.ts | 4 ++-- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/backend/src/services/organizations.services.ts b/src/backend/src/services/organizations.services.ts index 786f6b39b8..16e8e9c269 100644 --- a/src/backend/src/services/organizations.services.ts +++ b/src/backend/src/services/organizations.services.ts @@ -93,23 +93,22 @@ export default class OrganizationsService { if (!(await userHasPermission(submitter.userId, organization.organizationId, isAdmin))) { throw new AccessDeniedAdminOnlyException('update images'); } - + const applyInterestImageData = applyInterestImage ? await uploadFile(applyInterestImage) : null; const exploreAsGuestImageData = exploreAsGuestImage ? await uploadFile(exploreAsGuestImage) : null; - + const updateData = { ...(applyInterestImageData && { applyInterestImageId: applyInterestImageData.id }), ...(exploreAsGuestImageData && { exploreAsGuestImageId: exploreAsGuestImageData.id }) }; - + const newImages = await prisma.organization.update({ where: { organizationId: organization.organizationId }, data: updateData }); - + return newImages; } - /** Gets all the useful links for an organization @@ -155,4 +154,4 @@ export default class OrganizationsService { exploreAsGuestImage: organization.exploreAsGuestImageId }; } -} \ No newline at end of file +} diff --git a/src/backend/src/utils/google-integration.utils.ts b/src/backend/src/utils/google-integration.utils.ts index 57a296241d..95492860c4 100644 --- a/src/backend/src/utils/google-integration.utils.ts +++ b/src/backend/src/utils/google-integration.utils.ts @@ -336,4 +336,4 @@ export const deleteCalendarEvent = async (calendarId: string, eventId: string) = } catch (error: unknown) { throw error; } -}; \ No newline at end of file +}; diff --git a/src/frontend/src/apis/organization.api.ts b/src/frontend/src/apis/organization.api.ts index 21a831af67..2a8647ce3f 100644 --- a/src/frontend/src/apis/organization.api.ts +++ b/src/frontend/src/apis/organization.api.ts @@ -7,6 +7,5 @@ export const setOrganizationImages = (images: File[]) => { formData.append('applyInterestImage', images[0]); formData.append('exploreAsGuestImage', images[1]); - return axios.post<{ message: string }>(apiUrls.organizationsSetImages(), formData, { - }); + return axios.post<{ message: string }>(apiUrls.organizationsSetImages(), formData, {}); }; diff --git a/src/frontend/src/hooks/organizations.hooks.ts b/src/frontend/src/hooks/organizations.hooks.ts index a28b8dc43b..dc07459658 100644 --- a/src/frontend/src/hooks/organizations.hooks.ts +++ b/src/frontend/src/hooks/organizations.hooks.ts @@ -42,12 +42,11 @@ export const useSetOrganizationImages = () => { { onSuccess: () => { queryClient.invalidateQueries(['organizations']); - }, + } } ); }; - // Hook for child components to get the auth object export const useOrganization = () => { const context = useContext(OrganizationContext); diff --git a/src/shared/src/types/user-types.ts b/src/shared/src/types/user-types.ts index 42e3d9cd6e..db47c6bb40 100644 --- a/src/shared/src/types/user-types.ts +++ b/src/shared/src/types/user-types.ts @@ -43,8 +43,8 @@ export interface Organization { treasurer?: UserPreview; advisor?: UserPreview; description: string; - applyInterestImageId: string, - exploreAsGuestImageId: string + applyInterestImageId: string; + exploreAsGuestImageId: string; } /** From eed26d10eb6a27fe7341357576cbd17e7d756717 Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 2 Oct 2024 16:37:11 -0400 Subject: [PATCH 09/40] #2725 linting --- .../RecruitmentConfig/AdminToolsRecruitmentConfig.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index 45a8ff18d4..894ccb4f60 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -47,7 +47,7 @@ const AdminToolsRecruitmentConfig: React.FC = () => { }; fetchImages(); - }, [currentImages || imageUrls]); + }, [imageUrls]); if (organizationIsLoading) return ; if (organizationIsError) return ; From 9d36bd309202662303d73c31e527ced85d95d50c Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 2 Oct 2024 16:38:04 -0400 Subject: [PATCH 10/40] #2725 reverting some changes --- src/backend/src/utils/google-integration.utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/src/utils/google-integration.utils.ts b/src/backend/src/utils/google-integration.utils.ts index 95492860c4..57a296241d 100644 --- a/src/backend/src/utils/google-integration.utils.ts +++ b/src/backend/src/utils/google-integration.utils.ts @@ -336,4 +336,4 @@ export const deleteCalendarEvent = async (calendarId: string, eventId: string) = } catch (error: unknown) { throw error; } -}; +}; \ No newline at end of file From 04344f4a90c5483a656d854bae11eb9e5edf2642 Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 2 Oct 2024 16:40:37 -0400 Subject: [PATCH 11/40] #2725 prettier --- .../RecruitmentConfig/AdminToolsRecruitmentConfig.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index 894ccb4f60..8af3504c80 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -47,7 +47,7 @@ const AdminToolsRecruitmentConfig: React.FC = () => { }; fetchImages(); - }, [imageUrls]); + }, [currentImages]); if (organizationIsLoading) return ; if (organizationIsError) return ; From ffe776a96c5c6e228a6776be6fe3d9e7106beae0 Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 2 Oct 2024 16:42:07 -0400 Subject: [PATCH 12/40] prettier :( --- src/backend/src/utils/google-integration.utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/src/utils/google-integration.utils.ts b/src/backend/src/utils/google-integration.utils.ts index 57a296241d..95492860c4 100644 --- a/src/backend/src/utils/google-integration.utils.ts +++ b/src/backend/src/utils/google-integration.utils.ts @@ -336,4 +336,4 @@ export const deleteCalendarEvent = async (calendarId: string, eventId: string) = } catch (error: unknown) { throw error; } -}; \ No newline at end of file +}; From 25406fdd2376036d29616b131bda91796116fb7e Mon Sep 17 00:00:00 2001 From: harish Date: Tue, 22 Oct 2024 18:46:42 -0400 Subject: [PATCH 13/40] #2725 using NER upload button --- .../AdminToolsRecruitmentConfig.tsx | 116 +++++++----------- 1 file changed, 45 insertions(+), 71 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index 8af3504c80..41ba21d718 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -1,13 +1,13 @@ -import { Box, Grid, Typography, Button, CircularProgress } from '@mui/material'; +import { Box, Grid, Typography } from '@mui/material'; import MilestoneTable from './MilestoneTable'; import FAQsTable from './FAQTable'; import { useEffect, useMemo, useState } from 'react'; -import FileUploadIcon from '@mui/icons-material/FileUpload'; +import LoadingIndicator from '../../../components/LoadingIndicator'; +import ErrorPage from '../../ErrorPage'; import { useToast } from '../../../hooks/toasts.hooks'; import { useCurrentOrganization, useSetOrganizationImages } from '../../../hooks/organizations.hooks'; import { downloadGoogleImage } from '../../../apis/finance.api'; -import LoadingIndicator from '../../../components/LoadingIndicator'; -import ErrorPage from '../../ErrorPage'; +import NERUploadButton from '../../../components/NERUploadButton'; const AdminToolsRecruitmentConfig: React.FC = () => { const { @@ -16,9 +16,12 @@ const AdminToolsRecruitmentConfig: React.FC = () => { isError: organizationIsError, error: organizationError } = useCurrentOrganization(); - const { isLoading: organizationImagesIsLoading, mutateAsync: organizationImages } = useSetOrganizationImages(); + + const { mutateAsync: organizationImages } = useSetOrganizationImages(); const toast = useToast(); - const [imageUrls, setImageUrls] = useState<{ [key: string]: string | undefined }>({}); + + const [addedImage1, setAddedImage1] = useState(undefined); + const [addedImage2, setAddedImage2] = useState(undefined); const currentImages = useMemo(() => { return [organization?.applyInterestImageId, organization?.exploreAsGuestImageId]; @@ -40,7 +43,6 @@ const AdminToolsRecruitmentConfig: React.FC = () => { }); await Promise.all(imageFetches); - setImageUrls(newImageUrls); } catch (error) { console.error('Error fetching image URLs:', error); } @@ -92,78 +94,50 @@ const AdminToolsRecruitmentConfig: React.FC = () => { Recruitment Images - - - {organizationImagesIsLoading && } - - {currentImages[0] && ( - -
  • - -
  • -
    - )} -
    - - - - {organizationImagesIsLoading && } - - {currentImages[1] && ( - -
  • - -
  • -
    - )} +
    ); From 0b3fb3234b67c87e75990d2cdee6baf1ebf5298f Mon Sep 17 00:00:00 2001 From: harish Date: Sun, 27 Oct 2024 18:39:08 -0400 Subject: [PATCH 14/40] #2725 hooked it up to guest page --- src/backend/src/prisma/seed.ts | 4 +++- .../AdminToolsRecruitmentConfig.tsx | 2 +- .../src/pages/HomePage/GuestHomePage.tsx | 19 +++++++++++++++---- .../HomePage/components/ImageWithButton.tsx | 17 ++++++++++++++--- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/backend/src/prisma/seed.ts b/src/backend/src/prisma/seed.ts index 5a89606169..70443289bd 100644 --- a/src/backend/src/prisma/seed.ts +++ b/src/backend/src/prisma/seed.ts @@ -47,7 +47,9 @@ const performSeed: () => Promise = async () => { name: 'NER', userCreatedId: thomasEmrax.userId, description: - 'Northeastern Electric Racing is a student-run organization at Northeastern University building all-electric formula-style race cars from scratch to compete in Forumla Hybrid + Electric Formula SAE (FSAE).' + 'Northeastern Electric Racing is a student-run organization at Northeastern University building all-electric formula-style race cars from scratch to compete in Forumla Hybrid + Electric Formula SAE (FSAE).', + applyInterestImageId: '1_iak6ord4JP9TcR1sOYopyEs6EjTKQpw', + exploreAsGuestImageId: '1wRes7V_bMm9W7_3JCIDXYkMUiy6B3wRI' } }); diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index 41ba21d718..b1ed4ec6bd 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -57,7 +57,7 @@ const AdminToolsRecruitmentConfig: React.FC = () => { const handleFileUpload = async (files: File[], type: 'exploreAsGuest' | 'applyInterest') => { const validFiles: File[] = []; files.forEach((file) => { - if (file.size < 1000000) { + if (file.size < 5 * 1024 * 1024) { if (type === 'applyInterest') { validFiles[0] = file; } else if (type === 'exploreAsGuest') { diff --git a/src/frontend/src/pages/HomePage/GuestHomePage.tsx b/src/frontend/src/pages/HomePage/GuestHomePage.tsx index 97e36ed3e6..a0dfab08ea 100644 --- a/src/frontend/src/pages/HomePage/GuestHomePage.tsx +++ b/src/frontend/src/pages/HomePage/GuestHomePage.tsx @@ -6,11 +6,13 @@ import { routes } from '../../utils/routes'; import { useCurrentUser } from '../../hooks/users.hooks'; import { useEffect } from 'react'; import { useHomePageContext } from '../../app/HomePageContext'; +import { useCurrentOrganization } from '../../hooks/organizations.hooks'; const GuestHomePage = () => { const user = useCurrentUser(); const history = useHistory(); const { setOnGuestHomePage, setOnPNMHomePage } = useHomePageContext(); + const { data: organization } = useCurrentOrganization(); useEffect(() => { setOnGuestHomePage(true); @@ -22,17 +24,26 @@ const GuestHomePage = () => { {user ? `Welcome, ${user.firstName}!` : 'Welcome, Guest!'} - - + + history.push(routes.HOME_PNM)} /> { setOnGuestHomePage(false); diff --git a/src/frontend/src/pages/HomePage/components/ImageWithButton.tsx b/src/frontend/src/pages/HomePage/components/ImageWithButton.tsx index ba2b8f8b3c..aad0ae2179 100644 --- a/src/frontend/src/pages/HomePage/components/ImageWithButton.tsx +++ b/src/frontend/src/pages/HomePage/components/ImageWithButton.tsx @@ -12,12 +12,23 @@ interface ImageWithButtonProps { const ImageWithButton: React.FC = ({ title, imageSrc, buttonText, onClick }) => { return ( - + Date: Mon, 18 Nov 2024 19:54:23 -0500 Subject: [PATCH 15/40] #2725 some rehauling of google drive stuff --- .../AdminToolsRecruitmentConfig.tsx | 68 ++++++++++--------- .../src/pages/HomePage/GuestHomePage.tsx | 25 +++++-- src/frontend/src/utils/pipes.ts | 4 ++ yarn.lock | 6 +- 4 files changed, 63 insertions(+), 40 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index b1ed4ec6bd..f6bc79f70d 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -1,58 +1,44 @@ import { Box, Grid, Typography } from '@mui/material'; import MilestoneTable from './MilestoneTable'; import FAQsTable from './FAQTable'; -import { useEffect, useMemo, useState } from 'react'; -import LoadingIndicator from '../../../components/LoadingIndicator'; -import ErrorPage from '../../ErrorPage'; import { useToast } from '../../../hooks/toasts.hooks'; +import NERUploadButton from '../../../components/NERUploadButton'; +import { useEffect, useState } from 'react'; import { useCurrentOrganization, useSetOrganizationImages } from '../../../hooks/organizations.hooks'; import { downloadGoogleImage } from '../../../apis/finance.api'; -import NERUploadButton from '../../../components/NERUploadButton'; +import { blobPipe } from '../../../utils/pipes'; +import LoadingIndicator from '../../../components/LoadingIndicator'; const AdminToolsRecruitmentConfig: React.FC = () => { - const { - data: organization, - isLoading: organizationIsLoading, - isError: organizationIsError, - error: organizationError - } = useCurrentOrganization(); - const { mutateAsync: organizationImages } = useSetOrganizationImages(); const toast = useToast(); + const { data: organization } = useCurrentOrganization(); + + const [defaultImage1, setDefaultImage1] = useState(undefined); + const [defaultImage2, setDefaultImage2] = useState(undefined); + const [addedImage1, setAddedImage1] = useState(undefined); const [addedImage2, setAddedImage2] = useState(undefined); - const currentImages = useMemo(() => { - return [organization?.applyInterestImageId, organization?.exploreAsGuestImageId]; - }, [organization]); - useEffect(() => { - if (!currentImages.every(Boolean)) return; - const fetchImages = async () => { - try { - const newImageUrls: { [key: string]: string } = {}; + const applyBlob = await downloadGoogleImage(organization?.applyInterestImageId ?? ''); + const exploreBlob = await downloadGoogleImage(organization?.exploreAsGuestImageId ?? ''); - const imageFetches = currentImages.map(async (image) => { - if (image) { - const imageBlob = await downloadGoogleImage(image); - const url = URL.createObjectURL(imageBlob); - newImageUrls[image] = url; - } - }); + const applyFile = blobPipe(applyBlob, 'applyInterestImage.jpg'); + const exploreFile = blobPipe(exploreBlob, 'exploreAsGuestImage.jpg'); - await Promise.all(imageFetches); - } catch (error) { - console.error('Error fetching image URLs:', error); - } + setDefaultImage1(applyFile); + setDefaultImage2(exploreFile); }; fetchImages(); - }, [currentImages]); + }, [organization]); - if (organizationIsLoading) return ; - if (organizationIsError) return ; + if (!defaultImage1 || !defaultImage2) { + return ; + } const handleFileUpload = async (files: File[], type: 'exploreAsGuest' | 'applyInterest') => { const validFiles: File[] = []; @@ -99,6 +85,14 @@ const AdminToolsRecruitmentConfig: React.FC = () => { Apply Interest Image + {!addedImage1 && defaultImage1 ? ( + + ) : null} { @@ -121,6 +115,14 @@ const AdminToolsRecruitmentConfig: React.FC = () => { Explore As Guest Image + {!addedImage2 && defaultImage2 ? ( + + ) : null} { diff --git a/src/frontend/src/pages/HomePage/GuestHomePage.tsx b/src/frontend/src/pages/HomePage/GuestHomePage.tsx index a0dfab08ea..527fb404cf 100644 --- a/src/frontend/src/pages/HomePage/GuestHomePage.tsx +++ b/src/frontend/src/pages/HomePage/GuestHomePage.tsx @@ -4,9 +4,11 @@ import ImageWithButton from './components/ImageWithButton'; import { useHistory } from 'react-router-dom'; import { routes } from '../../utils/routes'; import { useCurrentUser } from '../../hooks/users.hooks'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useHomePageContext } from '../../app/HomePageContext'; import { useCurrentOrganization } from '../../hooks/organizations.hooks'; +import { downloadGoogleImage } from '../../apis/finance.api'; +import { downloadImageFile } from '../../../../backend/src/utils/google-integration.utils'; const GuestHomePage = () => { const user = useCurrentUser(); @@ -14,10 +16,24 @@ const GuestHomePage = () => { const { setOnGuestHomePage, setOnPNMHomePage } = useHomePageContext(); const { data: organization } = useCurrentOrganization(); + const [applyInterestImage, setApplyInterestImage] = useState(''); + const [exploreAsGuestImage, setExploreAsGuestImage] = useState(''); + useEffect(() => { setOnGuestHomePage(true); setOnPNMHomePage(false); - }, [setOnGuestHomePage, setOnPNMHomePage]); + + const fetchImages = async () => { + const applyBlob = await downloadImageFile(organization?.applyInterestImageId ?? ''); + const exploreBlob = await downloadImageFile(organization?.exploreAsGuestImageId ?? ''); + const applyImage = URL.createObjectURL(applyBlob); + const exploreImage = URL.createObjectURL(exploreBlob); + setApplyInterestImage(applyImage); + setExploreAsGuestImage(exploreImage); + }; + + fetchImages(); + }, [setOnGuestHomePage, setOnPNMHomePage, organization]); return ( @@ -37,13 +53,13 @@ const GuestHomePage = () => { history.push(routes.HOME_PNM)} /> { setOnGuestHomePage(false); @@ -55,4 +71,5 @@ const GuestHomePage = () => { ); }; + export default GuestHomePage; diff --git a/src/frontend/src/utils/pipes.ts b/src/frontend/src/utils/pipes.ts index 2a43db9058..af46698e83 100644 --- a/src/frontend/src/utils/pipes.ts +++ b/src/frontend/src/utils/pipes.ts @@ -28,6 +28,10 @@ export const weeksPipe = (weeks: number) => { return `${weeks} week${weeks === 1 ? '' : 's'}`; }; +export const blobPipe = (blob: Blob, fileName: string) => { + return new File([blob], fileName, { type: blob.type }); +}; + /** Display number as "$535" */ export const dollarsPipe = (dollars: number) => { return `$${dollars}`; diff --git a/yarn.lock b/yarn.lock index 6586f5c64b..88788b155e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6551,9 +6551,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30000981, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001125, caniuse-lite@npm:^1.0.30001587": - version: 1.0.30001629 - resolution: "caniuse-lite@npm:1.0.30001629" - checksum: c5e646e1b309b2a70b01499e0f0fca3a54bc111212f121b32614fe925b8f24ff6c1832a4306ddadf35678fbb11a6a97f953be07ccdc96bce5b530a4f84f40c45 + version: 1.0.30001680 + resolution: "caniuse-lite@npm:1.0.30001680" + checksum: 2641d2b18c5ab0a6663cb350c5adc81e5ede1a7677d1c7518a8053ada87bf6f206419e1820a2608f76fa5e4f7bea327cbe47df423783e571569a88c0ea645270 languageName: node linkType: hard From 522078c3719ba36b74c31f33efafdb26591226b3 Mon Sep 17 00:00:00 2001 From: harish Date: Tue, 3 Dec 2024 18:18:19 -0500 Subject: [PATCH 16/40] #2725 experimenting with iframe --- src/backend/index.ts | 2 +- .../src/pages/HomePage/GuestHomePage.tsx | 22 ++++++++----------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/backend/index.ts b/src/backend/index.ts index 97d683ec5f..8d9bd69c9f 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -29,7 +29,7 @@ const options: cors.CorsOptions = { 'http://localhost:3000', 'http://127.0.0.1:3000', 'https://finishlinebyner.com', - 'https://qa.finishlinebyner.com' + 'https://qa.finishlinebyner.com', ], methods: 'GET, POST, DELETE', credentials: true, diff --git a/src/frontend/src/pages/HomePage/GuestHomePage.tsx b/src/frontend/src/pages/HomePage/GuestHomePage.tsx index 527fb404cf..093c7bd913 100644 --- a/src/frontend/src/pages/HomePage/GuestHomePage.tsx +++ b/src/frontend/src/pages/HomePage/GuestHomePage.tsx @@ -9,12 +9,15 @@ import { useHomePageContext } from '../../app/HomePageContext'; import { useCurrentOrganization } from '../../hooks/organizations.hooks'; import { downloadGoogleImage } from '../../apis/finance.api'; import { downloadImageFile } from '../../../../backend/src/utils/google-integration.utils'; +import { imageDownloadUrl, imageFileUrl, imagePreviewUrl } from '../../utils/reimbursement-request.utils'; +import LoadingIndicator from '../../components/LoadingIndicator'; +import ErrorPage from '../ErrorPage'; const GuestHomePage = () => { const user = useCurrentUser(); const history = useHistory(); const { setOnGuestHomePage, setOnPNMHomePage } = useHomePageContext(); - const { data: organization } = useCurrentOrganization(); + const { data: organization, isError, error, isLoading } = useCurrentOrganization(); const [applyInterestImage, setApplyInterestImage] = useState(''); const [exploreAsGuestImage, setExploreAsGuestImage] = useState(''); @@ -22,19 +25,12 @@ const GuestHomePage = () => { useEffect(() => { setOnGuestHomePage(true); setOnPNMHomePage(false); - - const fetchImages = async () => { - const applyBlob = await downloadImageFile(organization?.applyInterestImageId ?? ''); - const exploreBlob = await downloadImageFile(organization?.exploreAsGuestImageId ?? ''); - const applyImage = URL.createObjectURL(applyBlob); - const exploreImage = URL.createObjectURL(exploreBlob); - setApplyInterestImage(applyImage); - setExploreAsGuestImage(exploreImage); - }; - - fetchImages(); }, [setOnGuestHomePage, setOnPNMHomePage, organization]); + if (!organization || isLoading) return ; + if (isError) return ; + + console.log(imagePreviewUrl(organization!.applyInterestImageId)) return ( @@ -53,7 +49,7 @@ const GuestHomePage = () => { history.push(routes.HOME_PNM)} /> From 82acc9ee03d0ab2f9f3b71218fc7b7acb3e770d5 Mon Sep 17 00:00:00 2001 From: harish Date: Sat, 14 Dec 2024 14:32:30 -0500 Subject: [PATCH 17/40] #2725 fixing syntax --- src/backend/src/prisma/seed.ts | 2 +- .../AdminToolsRecruitmentConfig.tsx | 19 +++++++- .../src/pages/HomePage/GuestHomePage.tsx | 17 ++----- .../HomePage/components/ImageWithButton.tsx | 44 ++++++++++++++++--- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/backend/src/prisma/seed.ts b/src/backend/src/prisma/seed.ts index 9fb50b4c91..9bb0864e2a 100644 --- a/src/backend/src/prisma/seed.ts +++ b/src/backend/src/prisma/seed.ts @@ -50,7 +50,7 @@ const performSeed: () => Promise = async () => { description: 'Northeastern Electric Racing is a student-run organization at Northeastern University building all-electric formula-style race cars from scratch to compete in Forumla Hybrid + Electric Formula SAE (FSAE).', applyInterestImageId: '1_iak6ord4JP9TcR1sOYopyEs6EjTKQpw', - exploreAsGuestImageId: '1wRes7V_bMm9W7_3JCIDXYkMUiy6B3wRI' + exploreAsGuestImageId: '1wRes7V_bMm9W7_3JCIDXYkMUiy6B3wRI', applicationLink: 'https://northeastern.campuslabs.com/engage/submitter/form/start/491315' } }); diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index 194321935d..9c1748cd3d 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -22,6 +22,9 @@ const AdminToolsRecruitmentConfig: React.FC = () => { const [addedImage1, setAddedImage1] = useState(undefined); const [addedImage2, setAddedImage2] = useState(undefined); + const [defaultImage1Url, setDefaultImage1Url] = useState(''); + const [defaultImage2Url, setDefaultImage2Url] = useState(''); + useEffect(() => { const fetchImages = async () => { const applyBlob = await downloadGoogleImage(organization?.applyInterestImageId ?? ''); @@ -32,9 +35,21 @@ const AdminToolsRecruitmentConfig: React.FC = () => { setDefaultImage1(applyFile); setDefaultImage2(exploreFile); + + // Create and cache URLs + const url1 = URL.createObjectURL(applyFile); + const url2 = URL.createObjectURL(exploreFile); + setDefaultImage1Url(url1); + setDefaultImage2Url(url2); }; fetchImages(); + + // Cleanup URLs when component unmounts + return () => { + if (defaultImage1Url) URL.revokeObjectURL(defaultImage1Url); + if (defaultImage2Url) URL.revokeObjectURL(defaultImage2Url); + }; }, [organization]); if (!defaultImage1 || !defaultImage2) { @@ -94,7 +109,7 @@ const AdminToolsRecruitmentConfig: React.FC = () => { component="img" sx={{ display: 'block', maxWidth: '200px', mb: 1 }} alt="Apply Interest" - src={URL.createObjectURL(defaultImage1)} + src={defaultImage1Url} /> ) : null} { component="img" sx={{ display: 'block', maxWidth: '200px', mb: 1 }} alt="Apply Interest" - src={URL.createObjectURL(defaultImage2)} + src={defaultImage2Url} /> ) : null} { const user = useCurrentUser(); const history = useHistory(); - const { setOnGuestHomePage, setOnPNMHomePage } = useHomePageContext(); const { data: organization, isError, error, isLoading } = useCurrentOrganization(); - - const [applyInterestImage, setApplyInterestImage] = useState(''); - const [exploreAsGuestImage, setExploreAsGuestImage] = useState(''); const { setOnGuestHomePage, setOnPNMHomePage, setOnOnboardingHomePage } = useHomePageContext(); useEffect(() => { setOnGuestHomePage(true); setOnPNMHomePage(false); - }, [setOnGuestHomePage, setOnPNMHomePage, organization]); + setOnOnboardingHomePage(false); + }, [setOnGuestHomePage, setOnPNMHomePage, setOnOnboardingHomePage]); if (!organization || isLoading) return ; if (isError) return ; - setOnOnboardingHomePage(false); - }, [setOnGuestHomePage, setOnPNMHomePage, setOnOnboardingHomePage]); - console.log(imagePreviewUrl(organization!.applyInterestImageId)) return ( @@ -58,7 +49,7 @@ const GuestHomePage = () => { /> { setOnGuestHomePage(false); diff --git a/src/frontend/src/pages/HomePage/components/ImageWithButton.tsx b/src/frontend/src/pages/HomePage/components/ImageWithButton.tsx index aad0ae2179..84fc11afc0 100644 --- a/src/frontend/src/pages/HomePage/components/ImageWithButton.tsx +++ b/src/frontend/src/pages/HomePage/components/ImageWithButton.tsx @@ -7,14 +7,29 @@ interface ImageWithButtonProps { imageSrc: string; buttonText: string; onClick: () => void; + showUploadInput?: boolean; + onFileChange?: (e: React.ChangeEvent) => void; + secondaryButton?: { + text: string; + onClick: () => void; + color?: 'success' | 'error' | 'primary'; + }; } -const ImageWithButton: React.FC = ({ title, imageSrc, buttonText, onClick }) => { +const ImageWithButton: React.FC = ({ + title, + imageSrc, + buttonText, + onClick, + showUploadInput, + onFileChange, + secondaryButton +}) => { return ( = ({ title, imageSrc, butt {title} - - {buttonText} - + {showUploadInput ? ( + + {buttonText} + + + ) : ( + + + {buttonText} + + {secondaryButton && ( + + {secondaryButton.text} + + )} + + )} ); From 3ad1c6a4d05bbc4c37cdbd501f35ae9e2b49e902 Mon Sep 17 00:00:00 2001 From: harish Date: Sat, 14 Dec 2024 14:36:06 -0500 Subject: [PATCH 18/40] #2725 prettier and linting --- src/backend/index.ts | 2 +- .../RecruitmentConfig/AdminToolsRecruitmentConfig.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/index.ts b/src/backend/index.ts index a4f154fc5f..cfe24e754e 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -30,7 +30,7 @@ const options: cors.CorsOptions = { 'http://localhost:3000', 'http://127.0.0.1:3000', 'https://finishlinebyner.com', - 'https://qa.finishlinebyner.com', + 'https://qa.finishlinebyner.com' ], methods: 'GET, POST, DELETE', credentials: true, diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index 9c1748cd3d..e4d3938d14 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -50,7 +50,7 @@ const AdminToolsRecruitmentConfig: React.FC = () => { if (defaultImage1Url) URL.revokeObjectURL(defaultImage1Url); if (defaultImage2Url) URL.revokeObjectURL(defaultImage2Url); }; - }, [organization]); + }, [organization, defaultImage1Url, defaultImage2Url]); if (!defaultImage1 || !defaultImage2) { return ; From b60ffc6e7e59adac94d8fa8a783698b3f9ec1985 Mon Sep 17 00:00:00 2001 From: harish Date: Mon, 16 Dec 2024 16:31:53 -0500 Subject: [PATCH 19/40] #2725 using different hook for fetching images --- .../src/controllers/onboarding.controller.ts | 24 +++++-- src/backend/src/routes/onboarding.routes.ts | 12 ++-- .../src/services/onboarding.services.ts | 70 +++++++++++-------- src/frontend/src/apis/finance.api.ts | 4 +- src/frontend/src/apis/onboarding.api.ts | 44 ++++++++++++ src/frontend/src/apis/organization.api.ts | 11 --- src/frontend/src/apis/organizations.api.ts | 9 +++ src/frontend/src/hooks/finance.hooks.ts | 6 +- src/frontend/src/hooks/onboarding.hooks.ts | 59 ++++++++++++++++ src/frontend/src/hooks/organizations.hooks.ts | 3 +- .../AdminToolsRecruitmentConfig.tsx | 62 ++++------------ .../TeamConfig/TeamTypeTable.tsx | 52 ++++++++------ src/frontend/src/utils/image.utils.ts | 6 ++ src/frontend/src/utils/urls.ts | 15 ++++ 14 files changed, 250 insertions(+), 127 deletions(-) create mode 100644 src/frontend/src/apis/onboarding.api.ts delete mode 100644 src/frontend/src/apis/organization.api.ts create mode 100644 src/frontend/src/hooks/onboarding.hooks.ts create mode 100644 src/frontend/src/utils/image.utils.ts diff --git a/src/backend/src/controllers/onboarding.controller.ts b/src/backend/src/controllers/onboarding.controller.ts index e171b1848d..8bf6fa106d 100644 --- a/src/backend/src/controllers/onboarding.controller.ts +++ b/src/backend/src/controllers/onboarding.controller.ts @@ -30,10 +30,9 @@ export default class OnboardingController { } } - static async getTeamTypeChecklists(req: Request, res: Response, next: NextFunction) { + static async getUsersChecklists(req: Request, res: Response, next: NextFunction) { try { - const { teamTypeIds } = req.body; - const checklists = OnboardingServices.getTeamTypeChecklists(teamTypeIds, req.organization); + const checklists = await OnboardingServices.getUsersChecklists(req.currentUser.userId, req.organization); res.status(200).json(checklists); } catch (error: unknown) { return next(error); @@ -89,4 +88,21 @@ export default class OnboardingController { return next(error); } } -} + + static async downloadImage(req: Request, res: Response, next: NextFunction) { + try { + const { fileId } = req.params; + + const imageData = await OnboardingServices.downloadImage(fileId); + + // Set the appropriate headers for the HTTP response + res.setHeader('content-type', String(imageData.type)); + res.setHeader('content-length', imageData.buffer.length); + + // Send the Buffer as the response body + res.send(imageData.buffer); + } catch (error: unknown) { + return next(error); + } + } +} \ No newline at end of file diff --git a/src/backend/src/routes/onboarding.routes.ts b/src/backend/src/routes/onboarding.routes.ts index 6ef63904b2..2c7e5d89cb 100644 --- a/src/backend/src/routes/onboarding.routes.ts +++ b/src/backend/src/routes/onboarding.routes.ts @@ -12,13 +12,7 @@ onboardingRouter.get('/checklists/general', OnboardingController.getGeneralCheck onboardingRouter.get('/checklists/checked', OnboardingController.getCheckedChecklists); -onboardingRouter.get( - '/checklist/teamTypeChecklists', - body('teamTypeIds').isArray(), - nonEmptyString(body('teamTypeIds.*')), - validateInputs, - OnboardingController.getTeamTypeChecklists -); +onboardingRouter.get('/checklists/usersChecklists', OnboardingController.getUsersChecklists); onboardingRouter.post( '/checklist/create', @@ -48,4 +42,6 @@ onboardingRouter.post( onboardingRouter.post('/checklist/delete/:checklistId', OnboardingController.deleteChecklist); -export default onboardingRouter; +onboardingRouter.get('/image/:fileId', OnboardingController.downloadImage); + +export default onboardingRouter; \ No newline at end of file diff --git a/src/backend/src/services/onboarding.services.ts b/src/backend/src/services/onboarding.services.ts index 79824c7672..5be08d86b3 100644 --- a/src/backend/src/services/onboarding.services.ts +++ b/src/backend/src/services/onboarding.services.ts @@ -3,6 +3,7 @@ import prisma from '../prisma/prisma'; import { userHasPermission } from '../utils/users.utils'; import { isAdmin } from 'shared'; import { AccessDeniedAdminOnlyException, DeletedException, HttpException, NotFoundException } from '../utils/errors.utils'; +import { downloadImageFile } from '../utils/google-integration.utils'; export default class OnboardingServices { /* Checklist section */ @@ -14,8 +15,8 @@ export default class OnboardingServices { */ static async getAllChecklists(organization: Organization) { const allChecklists = await prisma.checklist.findMany({ - where: { organizationId: organization.organizationId, dateDeleted: null }, - include: { subtasks: true } + where: { organizationId: organization.organizationId, dateDeleted: null, parentChecklistId: null }, + include: { subtasks: true, teamType: true } }); return allChecklists; @@ -27,11 +28,16 @@ export default class OnboardingServices { * @returns all the general checklists for the given organization */ static async getGeneralChecklists(organization: Organization) { - const generalChecklists = await prisma.checklist.findFirst({ - where: { organizationId: organization.organizationId, teamId: null, teamTypeId: null, dateDeleted: null }, - include: { subtasks: true } + const generalChecklists = await prisma.checklist.findMany({ + where: { + organizationId: organization.organizationId, + teamId: null, + teamTypeId: null, + dateDeleted: null, + parentChecklistId: null + }, + include: { subtasks: true, teamType: true } }); - return generalChecklists; } @@ -60,29 +66,38 @@ export default class OnboardingServices { * @param organization the organization of the checklists * @returns all the checklists for the given teamType Ids */ - static async getTeamTypeChecklists(teamTypeIds: string[], organization: Organization) { - if (teamTypeIds.length === 0) { - throw new HttpException(400, 'teamTypeIds cannot be empty'); + static async getUsersChecklists(userId: string, organization: Organization) { + const user = await prisma.user.findUnique({ where: { userId }, include: { teamsAsMember: true } }); + if (!user) { + throw new NotFoundException('User', userId); } - const teamTypes = await prisma.team_Type.findMany({ - where: { teamTypeId: { in: teamTypeIds } } - }); - - if (teamTypes.length !== teamTypeIds.length) { - throw new HttpException(400, 'One or more inavlid teamTypeIds'); - } + const teamTypeIds: string[] = user.teamsAsMember + .map((team) => team.teamTypeId) + .filter((id): id is string => id !== null); + const teamIds: string[] = user.teamsAsMember.map((team) => team.teamId).filter((id): id is string => id !== null); const teamTypeChecklists = await prisma.checklist.findMany({ where: { organizationId: organization.organizationId, dateDeleted: null, - teamTypeId: { in: teamTypeIds } + teamTypeId: { in: teamTypeIds }, + parentChecklistId: null + }, + include: { subtasks: true, teamType: true } + }); + + const teamChecklists = await prisma.checklist.findMany({ + where: { + organizationId: organization.organizationId, + dateDeleted: null, + teamId: { in: teamIds }, + parentChecklistId: null }, - include: { subtasks: true } + include: { subtasks: true, team: true } }); - return teamTypeChecklists; + return [...teamTypeChecklists, ...teamChecklists]; } /** @@ -115,20 +130,12 @@ export default class OnboardingServices { } if (!teamId && !teamTypeId) { - const generalChecklist = await prisma.checklist.findFirst({ - where: { organizationId: organization.organizationId, teamId: null, teamTypeId: null, dateDeleted: null } - }); - if (parentChecklistId) { const parentChecklist = await prisma.checklist.findFirst({ where: { checklistId: parentChecklistId } }); if (parentChecklist?.teamId || parentChecklist?.teamTypeId) { throw new HttpException(400, 'Parent checklist must also be a general checklist'); } } - - if (generalChecklist && !parentChecklistId) { - throw new HttpException(400, 'General checklist already exists'); - } } if (teamId) { @@ -313,4 +320,11 @@ export default class OnboardingServices { data: { dateDeleted: new Date(), userDeletedId: deleter.userId } }); } -} + + static async downloadImage(fileId: string) { + const fileData = await downloadImageFile(fileId); + + if (!fileData) throw new NotFoundException('Image File', fileId); + return fileData; + } +} \ No newline at end of file diff --git a/src/frontend/src/apis/finance.api.ts b/src/frontend/src/apis/finance.api.ts index 666ad7ebf8..6a20a065ff 100644 --- a/src/frontend/src/apis/finance.api.ts +++ b/src/frontend/src/apis/finance.api.ts @@ -196,7 +196,7 @@ export const denyReimbursementRequest = (id: string) => { * @param fileId the google id of the file to download * @returns the downloaded file as a Blob */ -export const downloadGoogleImage = async (fileId: string): Promise => { +export const downloadFinanceImage = async (fileId: string): Promise => { const response = await axios.get(apiUrls.financeImageById(fileId), { responseType: 'arraybuffer' // Set the response type to 'arraybuffer' to receive the image as a Buffer }); @@ -380,4 +380,4 @@ export const markPendingFinance = async (id: string) => { */ export const requestReimbursementRequestChanges = async (id: string) => { return axios.post(apiUrls.financeRequestChanges(id)); -}; +}; \ No newline at end of file diff --git a/src/frontend/src/apis/onboarding.api.ts b/src/frontend/src/apis/onboarding.api.ts new file mode 100644 index 0000000000..b46878bb3b --- /dev/null +++ b/src/frontend/src/apis/onboarding.api.ts @@ -0,0 +1,44 @@ +import { Checklist } from 'shared'; +import { apiUrls } from '../utils/urls'; +import axios from '../utils/axios'; + +/** + * API call to fetch all the checklists + */ +export const getAllChecklists = () => { + return axios.get(apiUrls.allChecklists(), { + transformResponse: (data) => JSON.parse(data) + }); +}; + +/** + * API call to fetch the general checklists + */ +export const getGeneralChecklists = () => { + return axios.get(apiUrls.generalChecklists(), { + transformResponse: (data) => JSON.parse(data) + }); +}; + +/** + * API call to fetch all the users checklists + */ +export const getUsersChecklists = () => { + return axios.get(apiUrls.usersTeamTypeChecklists(), { + transformResponse: (data) => JSON.parse(data) + }); +}; + +/** + * API Call to download a google image + * @param fileId file id to be downloaded + * @returns an image blob + */ +export const downloadGoogleImage = async (fileId: string): Promise => { + const response = await axios.get(apiUrls.imageById(fileId), { + responseType: 'arraybuffer' // Set the response type to 'arraybuffer' to receive the image as a Buffer + }); + const imageBuffer = new Uint8Array(response.data); + const imageBlob = new Blob([imageBuffer], { type: response.headers['content-type'] }); + return imageBlob; +}; \ No newline at end of file diff --git a/src/frontend/src/apis/organization.api.ts b/src/frontend/src/apis/organization.api.ts deleted file mode 100644 index 2a8647ce3f..0000000000 --- a/src/frontend/src/apis/organization.api.ts +++ /dev/null @@ -1,11 +0,0 @@ -import axios from '../utils/axios'; -import { apiUrls } from '../utils/urls'; - -export const setOrganizationImages = (images: File[]) => { - const formData = new FormData(); - - formData.append('applyInterestImage', images[0]); - formData.append('exploreAsGuestImage', images[1]); - - return axios.post<{ message: string }>(apiUrls.organizationsSetImages(), formData, {}); -}; diff --git a/src/frontend/src/apis/organizations.api.ts b/src/frontend/src/apis/organizations.api.ts index 3eb4fb3756..bfa6c8522e 100644 --- a/src/frontend/src/apis/organizations.api.ts +++ b/src/frontend/src/apis/organizations.api.ts @@ -11,3 +11,12 @@ export const getCurrentOrganization = async () => { transformResponse: (data) => JSON.parse(data) }); }; + +export const setOrganizationImages = (images: File[]) => { + const formData = new FormData(); + + formData.append('applyInterestImage', images[0]); + formData.append('exploreAsGuestImage', images[1]); + + return axios.post<{ message: string }>(apiUrls.organizationsSetImages(), formData, {}); +}; diff --git a/src/frontend/src/hooks/finance.hooks.ts b/src/frontend/src/hooks/finance.hooks.ts index c3ecbe93e4..14d9452323 100644 --- a/src/frontend/src/hooks/finance.hooks.ts +++ b/src/frontend/src/hooks/finance.hooks.ts @@ -9,7 +9,7 @@ import { deleteReimbursementRequest, denyReimbursementRequest, downloadBlobsToPdf, - downloadGoogleImage, + downloadFinanceImage, editReimbursementRequest, getAllReimbursementRequests, getAllReimbursements, @@ -376,7 +376,7 @@ export const useDenyReimbursementRequest = (id: string) => { export const useDownloadPDFOfImages = () => { return useMutation(['reimbursement-requests'], async (formData: DownloadReceiptsFormInput) => { const promises = formData.fileIds.map((fileId) => { - return downloadGoogleImage(fileId); + return downloadFinanceImage(fileId); }); const blobs = await Promise.all(promises); @@ -583,4 +583,4 @@ export const useRequestReimbursementRequestChanges = (id: string) => { } } ); -}; +}; \ No newline at end of file diff --git a/src/frontend/src/hooks/onboarding.hooks.ts b/src/frontend/src/hooks/onboarding.hooks.ts new file mode 100644 index 0000000000..0442288048 --- /dev/null +++ b/src/frontend/src/hooks/onboarding.hooks.ts @@ -0,0 +1,59 @@ +import { useQuery } from 'react-query'; +import { Checklist } from 'shared'; +import { getAllChecklists, getGeneralChecklists, getUsersChecklists, downloadGoogleImage } from '../apis/onboarding.api'; + +export const useAllChecklists = () => { + return useQuery(['checklists'], async () => { + const { data } = await getAllChecklists(); + return data; + }); +}; + +export const useGeneralChecklists = () => { + return useQuery(['checklists', 'general'], async () => { + const { data } = await getGeneralChecklists(); + return data; + }); +}; + +export const useUsersTeamTypeChecklists = () => { + return useQuery(['checklists', 'teamTypeChecklists'], async () => { + const { data } = await getUsersChecklists(); + return data; + }); +}; + +export const useGetImageUrl = (imageFileId: string | null) => { + return useQuery( + ['image', imageFileId], + async () => { + if (!imageFileId) throw new Error('No image ID provided'); + const imageBlob = await downloadGoogleImage(imageFileId); + return URL.createObjectURL(imageBlob); + }, + { + enabled: !!imageFileId + } + ); +}; + +export const useGetImageUrls = (imageFileIds: (string | null)[]) => { + return useQuery( + ['image', imageFileIds], + async () => { + if (!imageFileIds) throw new Error('No image ID provided'); + const imageBlobs = await Promise.all( + imageFileIds + .filter((id): id is string => id !== null) + .map(async (imageId) => { + const imageBlob = await downloadGoogleImage(imageId); + return URL.createObjectURL(imageBlob); + }) + ); + return imageBlobs; + }, + { + enabled: !!imageFileIds + } + ); +}; \ No newline at end of file diff --git a/src/frontend/src/hooks/organizations.hooks.ts b/src/frontend/src/hooks/organizations.hooks.ts index dc07459658..d30372bf98 100644 --- a/src/frontend/src/hooks/organizations.hooks.ts +++ b/src/frontend/src/hooks/organizations.hooks.ts @@ -2,8 +2,7 @@ import { useContext, useState } from 'react'; import { OrganizationContext } from '../app/AppOrganizationContext'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import { Organization } from 'shared'; -import { getCurrentOrganization } from '../apis/organizations.api'; -import { setOrganizationImages } from '../apis/organization.api'; +import { getCurrentOrganization, setOrganizationImages } from '../apis/organizations.api'; interface OrganizationProvider { organizationId: string; diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index e4d3938d14..340503b253 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -3,56 +3,27 @@ import MilestoneTable from './MilestoneTable'; import FAQsTable from './FAQTable'; import { useToast } from '../../../hooks/toasts.hooks'; import NERUploadButton from '../../../components/NERUploadButton'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useCurrentOrganization, useSetOrganizationImages } from '../../../hooks/organizations.hooks'; -import { downloadGoogleImage } from '../../../apis/finance.api'; -import { blobPipe } from '../../../utils/pipes'; import LoadingIndicator from '../../../components/LoadingIndicator'; -import ApplicationLinkTable from './ApplicationLinkTable'; +import { useGetImageUrl } from '../../../hooks/onboarding.hooks'; const AdminToolsRecruitmentConfig: React.FC = () => { const { mutateAsync: organizationImages } = useSetOrganizationImages(); const toast = useToast(); - const { data: organization } = useCurrentOrganization(); - const [defaultImage1, setDefaultImage1] = useState(undefined); - const [defaultImage2, setDefaultImage2] = useState(undefined); - const [addedImage1, setAddedImage1] = useState(undefined); const [addedImage2, setAddedImage2] = useState(undefined); - const [defaultImage1Url, setDefaultImage1Url] = useState(''); - const [defaultImage2Url, setDefaultImage2Url] = useState(''); - - useEffect(() => { - const fetchImages = async () => { - const applyBlob = await downloadGoogleImage(organization?.applyInterestImageId ?? ''); - const exploreBlob = await downloadGoogleImage(organization?.exploreAsGuestImageId ?? ''); - - const applyFile = blobPipe(applyBlob, 'applyInterestImage.jpg'); - const exploreFile = blobPipe(exploreBlob, 'exploreAsGuestImage.jpg'); - - setDefaultImage1(applyFile); - setDefaultImage2(exploreFile); - - // Create and cache URLs - const url1 = URL.createObjectURL(applyFile); - const url2 = URL.createObjectURL(exploreFile); - setDefaultImage1Url(url1); - setDefaultImage2Url(url2); - }; - - fetchImages(); - - // Cleanup URLs when component unmounts - return () => { - if (defaultImage1Url) URL.revokeObjectURL(defaultImage1Url); - if (defaultImage2Url) URL.revokeObjectURL(defaultImage2Url); - }; - }, [organization, defaultImage1Url, defaultImage2Url]); + const { data: applyInterestImageUrl, isLoading: applyImageLoading } = useGetImageUrl( + organization?.applyInterestImageId ?? null + ); + const { data: exploreGuestImageUrl, isLoading: exploreImageLoading } = useGetImageUrl( + organization?.exploreAsGuestImageId ?? null + ); - if (!defaultImage1 || !defaultImage2) { + if (!organization || applyImageLoading || exploreImageLoading) { return ; } @@ -91,9 +62,6 @@ const AdminToolsRecruitmentConfig: React.FC = () => { - - - Recruitment Images @@ -104,14 +72,14 @@ const AdminToolsRecruitmentConfig: React.FC = () => { Apply Interest Image - {!addedImage1 && defaultImage1 ? ( + {!addedImage1 && applyInterestImageUrl && ( - ) : null} + )} { @@ -134,14 +102,14 @@ const AdminToolsRecruitmentConfig: React.FC = () => { Explore As Guest Image - {!addedImage2 && defaultImage2 ? ( + {!addedImage2 && exploreGuestImageUrl && ( - ) : null} + )} { diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index 50c6000a70..578808e9cf 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -7,10 +7,10 @@ import CreateTeamTypeFormModal from './CreateTeamTypeFormModal'; import { TeamType } from 'shared'; import EditTeamTypeFormModal from './EditTeamTypeFormModal'; import { useAllTeamTypes, useSetTeamTypeImage } from '../../../hooks/team-types.hooks'; -import { useEffect, useState } from 'react'; +import { useMemo, useState } from 'react'; import { useToast } from '../../../hooks/toasts.hooks'; import NERUploadButton from '../../../components/NERUploadButton'; -import { downloadGoogleImage } from '../../../apis/finance.api'; +import { useGetImageUrls } from '../../../hooks/onboarding.hooks'; const TeamTypeTable: React.FC = () => { const { @@ -24,35 +24,43 @@ const TeamTypeTable: React.FC = () => { const [editingTeamType, setEditingTeamType] = useState(undefined); const [addedImages, setAddedImages] = useState<{ [key: string]: File | undefined }>({}); const toast = useToast(); - const [imageUrls, setImageUrls] = useState<{ [key: string]: string | undefined }>({}); - useEffect(() => { - try { - teamTypes?.forEach(async (teamType) => { - const imageBlob = await downloadGoogleImage(teamType.imageFileId ?? ''); - const url = URL.createObjectURL(imageBlob); - setImageUrls((prev) => ({ ...prev, [teamType.teamTypeId]: url })); - }); - } catch (error) { - console.error('Error fetching image urls', error); - } - }, [teamTypes]); + const imageFileIds = teamTypes?.map((teamType) => teamType.imageFileId) ?? []; + const { data: imageUrlsList, isLoading, isError } = useGetImageUrls(imageFileIds); + const { mutateAsync: setTeamTypeImage, isLoading: setTeamTypeIsLoading } = useSetTeamTypeImage(); - const { mutateAsync: setTeamTypeImage } = useSetTeamTypeImage(); + const imageUrls = useMemo(() => { + if (!imageUrlsList || isLoading || isError) return {}; + + const urlMap: { [key: string]: string | undefined } = {}; + teamTypes?.forEach((teamType, index) => { + urlMap[teamType.teamTypeId] = imageUrlsList[index]; + }); + return urlMap; + }, [imageUrlsList, isLoading, isError, teamTypes]); - if (!teamTypes || teamTypesIsLoading) { - return ; - } if (teamTypesIsError) { return ; } + if (!teamTypes || teamTypesIsLoading || setTeamTypeIsLoading) { + return ; + } + const onSubmitTeamTypeImage = async (teamTypeId: string) => { const addedImage = addedImages[teamTypeId]; if (addedImage) { - await setTeamTypeImage({ file: addedImage, id: teamTypeId }); - toast.success('Image uploaded successfully!', 5000); - setAddedImages((prev) => ({ ...prev, [teamTypeId]: undefined })); + try { + await setTeamTypeImage({ file: addedImage, id: teamTypeId }); + toast.success('Image uploaded successfully!', 5000); + setAddedImages((prev) => ({ ...prev, [teamTypeId]: undefined })); + } catch (error) { + if (error instanceof Error) { + toast.error('Failed to set team image: ' + error.message); + } else { + toast.error('Failed to set team image'); + } + } } else { toast.error('No image selected for upload.', 5000); } @@ -152,4 +160,4 @@ const TeamTypeTable: React.FC = () => { ); }; -export default TeamTypeTable; +export default TeamTypeTable; \ No newline at end of file diff --git a/src/frontend/src/utils/image.utils.ts b/src/frontend/src/utils/image.utils.ts new file mode 100644 index 0000000000..c855e88bb8 --- /dev/null +++ b/src/frontend/src/utils/image.utils.ts @@ -0,0 +1,6 @@ +import { downloadFinanceImage } from '../apis/finance.api'; + +export const getImageUrl = async (imageFileId: string) => { + const imageBlob = await downloadFinanceImage(imageFileId); + return URL.createObjectURL(imageBlob); +}; diff --git a/src/frontend/src/utils/urls.ts b/src/frontend/src/utils/urls.ts index c2129b7813..08b60e1376 100644 --- a/src/frontend/src/utils/urls.ts +++ b/src/frontend/src/utils/urls.ts @@ -176,6 +176,7 @@ const currentOrganization = () => `${organizations()}/current`; const organizationsUsefulLinks = () => `${organizations()}/useful-links`; const organizationsSetUsefulLinks = () => `${organizationsUsefulLinks()}/set`; const organizationsSetImages = () => `${organizations()}/images/update`; +const organizationsGetImages = () => `${organizations()}/images`; /******************* Car Endpoints ********************/ const cars = () => `${API_URL}/cars`; @@ -192,6 +193,13 @@ const faqCreate = () => `${recruitment()}/faq/create`; const faqEdit = (id: string) => `${recruitment()}/faq/${id}/edit`; const faqDelete = (id: string) => `${recruitment()}/faq/${id}/delete`; +/************** Onboarding Endpoints ***************/ +const onboarding = () => `${API_URL}/onboarding`; +const allChecklists = () => `${onboarding()}/checklists`; +const generalChecklists = () => `${allChecklists()}/general`; +const usersTeamTypeChecklists = () => `${allChecklists()}/usersChecklists`; +const imageById = (imageId: string) => `${onboarding()}/image/${imageId}`; + /**************** Other Endpoints ****************/ const version = () => `https://api.github.com/repos/Northeastern-Electric-Racing/FinishLine/releases/latest`; @@ -334,6 +342,7 @@ export const apiUrls = { organizationsUsefulLinks, organizationsSetUsefulLinks, organizationsSetImages, + organizationsGetImages, cars, carsCreate, @@ -347,5 +356,11 @@ export const apiUrls = { faqEdit, faqDelete, + onboarding, + allChecklists, + generalChecklists, + usersTeamTypeChecklists, + imageById, + version }; From 71a215646276cd1aa6b3d5ed143383b04d9905cd Mon Sep 17 00:00:00 2001 From: harish Date: Mon, 16 Dec 2024 18:58:44 -0500 Subject: [PATCH 20/40] #2725 linting and prettier --- src/backend/src/controllers/onboarding.controller.ts | 2 +- src/backend/src/routes/onboarding.routes.ts | 2 +- src/backend/src/services/onboarding.services.ts | 2 +- src/frontend/src/apis/finance.api.ts | 2 +- src/frontend/src/apis/onboarding.api.ts | 1 - src/frontend/src/hooks/finance.hooks.ts | 2 +- src/frontend/src/hooks/onboarding.hooks.ts | 2 +- .../src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx | 2 +- src/frontend/src/utils/urls.ts | 1 + 9 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/backend/src/controllers/onboarding.controller.ts b/src/backend/src/controllers/onboarding.controller.ts index 8bf6fa106d..57cb960d79 100644 --- a/src/backend/src/controllers/onboarding.controller.ts +++ b/src/backend/src/controllers/onboarding.controller.ts @@ -105,4 +105,4 @@ export default class OnboardingController { return next(error); } } -} \ No newline at end of file +} diff --git a/src/backend/src/routes/onboarding.routes.ts b/src/backend/src/routes/onboarding.routes.ts index 2c7e5d89cb..f97d434880 100644 --- a/src/backend/src/routes/onboarding.routes.ts +++ b/src/backend/src/routes/onboarding.routes.ts @@ -44,4 +44,4 @@ onboardingRouter.post('/checklist/delete/:checklistId', OnboardingController.del onboardingRouter.get('/image/:fileId', OnboardingController.downloadImage); -export default onboardingRouter; \ No newline at end of file +export default onboardingRouter; diff --git a/src/backend/src/services/onboarding.services.ts b/src/backend/src/services/onboarding.services.ts index 5be08d86b3..c6924d2fd2 100644 --- a/src/backend/src/services/onboarding.services.ts +++ b/src/backend/src/services/onboarding.services.ts @@ -327,4 +327,4 @@ export default class OnboardingServices { if (!fileData) throw new NotFoundException('Image File', fileId); return fileData; } -} \ No newline at end of file +} diff --git a/src/frontend/src/apis/finance.api.ts b/src/frontend/src/apis/finance.api.ts index 6a20a065ff..4e068f507b 100644 --- a/src/frontend/src/apis/finance.api.ts +++ b/src/frontend/src/apis/finance.api.ts @@ -380,4 +380,4 @@ export const markPendingFinance = async (id: string) => { */ export const requestReimbursementRequestChanges = async (id: string) => { return axios.post(apiUrls.financeRequestChanges(id)); -}; \ No newline at end of file +}; diff --git a/src/frontend/src/apis/onboarding.api.ts b/src/frontend/src/apis/onboarding.api.ts index 2fb2ad7c90..dff34e17a3 100644 --- a/src/frontend/src/apis/onboarding.api.ts +++ b/src/frontend/src/apis/onboarding.api.ts @@ -42,4 +42,3 @@ export const downloadGoogleImage = async (fileId: string): Promise => { const imageBlob = new Blob([imageBuffer], { type: response.headers['content-type'] }); return imageBlob; }; - diff --git a/src/frontend/src/hooks/finance.hooks.ts b/src/frontend/src/hooks/finance.hooks.ts index 14d9452323..b93dfebaee 100644 --- a/src/frontend/src/hooks/finance.hooks.ts +++ b/src/frontend/src/hooks/finance.hooks.ts @@ -583,4 +583,4 @@ export const useRequestReimbursementRequestChanges = (id: string) => { } } ); -}; \ No newline at end of file +}; diff --git a/src/frontend/src/hooks/onboarding.hooks.ts b/src/frontend/src/hooks/onboarding.hooks.ts index 0442288048..2194afa9ba 100644 --- a/src/frontend/src/hooks/onboarding.hooks.ts +++ b/src/frontend/src/hooks/onboarding.hooks.ts @@ -56,4 +56,4 @@ export const useGetImageUrls = (imageFileIds: (string | null)[]) => { enabled: !!imageFileIds } ); -}; \ No newline at end of file +}; diff --git a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx index 578808e9cf..05ab96fc43 100644 --- a/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx +++ b/src/frontend/src/pages/AdminToolsPage/TeamConfig/TeamTypeTable.tsx @@ -160,4 +160,4 @@ const TeamTypeTable: React.FC = () => { ); }; -export default TeamTypeTable; \ No newline at end of file +export default TeamTypeTable; diff --git a/src/frontend/src/utils/urls.ts b/src/frontend/src/utils/urls.ts index 77b526a4f4..e8b73ca141 100644 --- a/src/frontend/src/utils/urls.ts +++ b/src/frontend/src/utils/urls.ts @@ -361,4 +361,5 @@ export const apiUrls = { generalChecklists, usersTeamTypeChecklists, imageById, + version }; From 681e8efa8a5c63b45e95d6cd489e4084e7b56532 Mon Sep 17 00:00:00 2001 From: harish Date: Mon, 16 Dec 2024 19:04:49 -0500 Subject: [PATCH 21/40] #2725 changing useful links import --- src/frontend/src/pages/AdminToolsPage/AdminToolsRecruitment.tsx | 2 +- .../ProjectsConfig/UsefulLinks/UsefulLinksTable.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 src/frontend/src/pages/AdminToolsPage/ProjectsConfig/UsefulLinks/UsefulLinksTable.tsx diff --git a/src/frontend/src/pages/AdminToolsPage/AdminToolsRecruitment.tsx b/src/frontend/src/pages/AdminToolsPage/AdminToolsRecruitment.tsx index 39286b6bc9..ff4cdd052c 100644 --- a/src/frontend/src/pages/AdminToolsPage/AdminToolsRecruitment.tsx +++ b/src/frontend/src/pages/AdminToolsPage/AdminToolsRecruitment.tsx @@ -4,7 +4,7 @@ import WorkPackageTemplateTable from './ProjectsConfig/WorkPackageTemplateTable' import LinkTypeTable from './ProjectsConfig/LinkTypes/LinkTypeTable'; import DescriptionBulletTypeTable from './ProjectsConfig/DescriptionBulletTypes/DescriptionBulletTypeTable'; import CarsTable from './ProjectsConfig/CarsTable'; -import UsefulLinksTable from './ProjectsConfig/UsefulLinks/UsefulLinksTable'; +import UsefulLinksTable from './OnboardingConfig/UsefulLinks/UsefulLinksTable'; const AdminToolsProjectsConfig: React.FC = () => { return ( diff --git a/src/frontend/src/pages/AdminToolsPage/ProjectsConfig/UsefulLinks/UsefulLinksTable.tsx b/src/frontend/src/pages/AdminToolsPage/ProjectsConfig/UsefulLinks/UsefulLinksTable.tsx new file mode 100644 index 0000000000..0519ecba6e --- /dev/null +++ b/src/frontend/src/pages/AdminToolsPage/ProjectsConfig/UsefulLinks/UsefulLinksTable.tsx @@ -0,0 +1 @@ + \ No newline at end of file From 7944b83689b9761866cbc8ec2be75785a6f466cd Mon Sep 17 00:00:00 2001 From: harish Date: Mon, 16 Dec 2024 19:07:33 -0500 Subject: [PATCH 22/40] #2725 removing unused file --- .../ProjectsConfig/UsefulLinks/UsefulLinksTable.tsx | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/frontend/src/pages/AdminToolsPage/ProjectsConfig/UsefulLinks/UsefulLinksTable.tsx diff --git a/src/frontend/src/pages/AdminToolsPage/ProjectsConfig/UsefulLinks/UsefulLinksTable.tsx b/src/frontend/src/pages/AdminToolsPage/ProjectsConfig/UsefulLinks/UsefulLinksTable.tsx deleted file mode 100644 index 0519ecba6e..0000000000 --- a/src/frontend/src/pages/AdminToolsPage/ProjectsConfig/UsefulLinks/UsefulLinksTable.tsx +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 299689a01aa384897a5e551abac51378ac0d926d Mon Sep 17 00:00:00 2001 From: harish Date: Tue, 17 Dec 2024 16:51:01 -0500 Subject: [PATCH 23/40] #2725 using the new hook in the guest page --- .../src/pages/HomePage/GuestHomePage.tsx | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/pages/HomePage/GuestHomePage.tsx b/src/frontend/src/pages/HomePage/GuestHomePage.tsx index 53eb8b78f5..4de4be5197 100644 --- a/src/frontend/src/pages/HomePage/GuestHomePage.tsx +++ b/src/frontend/src/pages/HomePage/GuestHomePage.tsx @@ -9,6 +9,7 @@ import { useHomePageContext } from '../../app/HomePageContext'; import { useCurrentOrganization } from '../../hooks/organizations.hooks'; import LoadingIndicator from '../../components/LoadingIndicator'; import ErrorPage from '../ErrorPage'; +import { useGetImageUrl } from '../../hooks/onboarding.hooks'; const GuestHomePage = () => { const user = useCurrentUser(); @@ -16,12 +17,26 @@ const GuestHomePage = () => { const { data: organization, isError, error, isLoading } = useCurrentOrganization(); const { setCurrentHomePage } = useHomePageContext(); + const { + data: applyInterestImageUrl, + isLoading: applyImageLoading, + isError: applyImageError + } = useGetImageUrl(organization?.applyInterestImageId ?? null); + const { + data: exploreGuestImageUrl, + isLoading: exploreImageLoading, + isError: exploreImageError + } = useGetImageUrl(organization?.exploreAsGuestImageId ?? null); + useEffect(() => { setCurrentHomePage('guest'); }, [setCurrentHomePage]); - if (!organization || isLoading) return ; + if (!organization || isLoading || applyImageLoading || exploreImageLoading) return ; if (isError) return ; + if (applyImageError) return ; + if (exploreImageError) return ; + if (!applyInterestImageUrl || !exploreGuestImageUrl) throw new Error('Image URLs are undefined'); return ( @@ -41,13 +56,13 @@ const GuestHomePage = () => { history.push(routes.HOME_PNM)} /> history.push(routes.HOME_MEMBER)} /> From 1a29318d56c5a8e60e4d8b569a541aa38e46a65c Mon Sep 17 00:00:00 2001 From: harish Date: Tue, 17 Dec 2024 16:52:47 -0500 Subject: [PATCH 24/40] #2725 weird prettier thing --- src/frontend/src/hooks/organizations.hooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/hooks/organizations.hooks.ts b/src/frontend/src/hooks/organizations.hooks.ts index f46aee41d8..22efb992f5 100644 --- a/src/frontend/src/hooks/organizations.hooks.ts +++ b/src/frontend/src/hooks/organizations.hooks.ts @@ -2,7 +2,7 @@ import { useContext, useState } from 'react'; import { OrganizationContext } from '../app/AppOrganizationContext'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import { Organization } from 'shared'; -import { getCurrentOrganization, setOrganizationImages, setOnboardingText } from '../apis/organizations.api'; +import { getCurrentOrganization, setOrganizationImages, setOnboardingText } from '../apis/organizations.api'; interface OrganizationProvider { organizationId: string; From f65124fabd56262fc003b4a5fa04e54cc535b922 Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 18 Dec 2024 10:24:59 -0500 Subject: [PATCH 25/40] #2725 typescript --- src/frontend/src/hooks/organizations.hooks.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/hooks/organizations.hooks.ts b/src/frontend/src/hooks/organizations.hooks.ts index ba3ff0eb4d..4058646a6a 100644 --- a/src/frontend/src/hooks/organizations.hooks.ts +++ b/src/frontend/src/hooks/organizations.hooks.ts @@ -2,7 +2,12 @@ import { useContext, useState } from 'react'; import { OrganizationContext } from '../app/AppOrganizationContext'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import { Organization } from 'shared'; -import { getCurrentOrganization, setOrganizationImages, setOnboardingText } from '../apis/organizations.api'; +import { + getCurrentOrganization, + setOrganizationImages, + setOnboardingText, + updateOrganizationContacts +} from '../apis/organizations.api'; interface OrganizationProvider { organizationId: string; From 2ad45190181490b445754a4f5a20de304b77fc61 Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 18 Dec 2024 10:58:13 -0500 Subject: [PATCH 26/40] #2725 removing unecessary endpoint --- .../controllers/organizations.controller.ts | 9 ------- .../src/routes/organizations.routes.ts | 2 -- .../src/services/organizations.services.ts | 21 ---------------- .../tests/unmocked/organization.test.ts | 24 ------------------- 4 files changed, 56 deletions(-) diff --git a/src/backend/src/controllers/organizations.controller.ts b/src/backend/src/controllers/organizations.controller.ts index e425d14280..3a863c4355 100644 --- a/src/backend/src/controllers/organizations.controller.ts +++ b/src/backend/src/controllers/organizations.controller.ts @@ -52,15 +52,6 @@ export default class OrganizationsController { } } - static async getOrganizationImages(req: Request, res: Response, next: NextFunction) { - try { - const images = await OrganizationsService.getOrganizationImages(req.organization.organizationId); - res.status(200).json(images); - } catch (error: unknown) { - next(error); - } - } - static async updateApplicationLink(req: Request, res: Response, next: NextFunction) { try { const { applicationLink } = req.body; diff --git a/src/backend/src/routes/organizations.routes.ts b/src/backend/src/routes/organizations.routes.ts index c932000ea9..ad687c2279 100644 --- a/src/backend/src/routes/organizations.routes.ts +++ b/src/backend/src/routes/organizations.routes.ts @@ -19,8 +19,6 @@ organizationRouter.post( OrganizationsController.setImages ); -organizationRouter.get('/images', OrganizationsController.getOrganizationImages); - organizationRouter.post( '/application-link/update', nonEmptyString(body('applicationLink')), diff --git a/src/backend/src/services/organizations.services.ts b/src/backend/src/services/organizations.services.ts index aadcd9a808..40535f5b10 100644 --- a/src/backend/src/services/organizations.services.ts +++ b/src/backend/src/services/organizations.services.ts @@ -141,27 +141,6 @@ export default class OrganizationsService { return links.map(linkTransformer); } - /** - * Gets all organization Images for the given organization Id - * @param organizationId organization Id of the milestone - * @returns all the milestones from the given organization - */ - - static async getOrganizationImages(organizationId: string) { - const organization = await prisma.organization.findUnique({ - where: { organizationId } - }); - - if (!organization) { - throw new NotFoundException('Organization', organizationId); - } - - return { - applyInterestImage: organization.applyInterestImageId, - exploreAsGuestImage: organization.exploreAsGuestImageId - }; - } - /** * Updates the application link for the given organization Id * @param submitter the user who is setting the links diff --git a/src/backend/tests/unmocked/organization.test.ts b/src/backend/tests/unmocked/organization.test.ts index 38d179e23a..68e589147a 100644 --- a/src/backend/tests/unmocked/organization.test.ts +++ b/src/backend/tests/unmocked/organization.test.ts @@ -183,30 +183,6 @@ describe('Organization Tests', () => { }); }); - describe('Get Organization Images', () => { - it('Fails if an organization does not exist', async () => { - await expect(async () => await OrganizationsService.getOrganizationImages('1')).rejects.toThrow( - new NotFoundException('Organization', '1') - ); - }); - - it('Succeeds and gets all the images', async () => { - const testBatman = await createTestUser(batmanAppAdmin, orgId); - await createTestLinkType(testBatman, orgId); - await OrganizationsService.setImages( - { originalname: 'image1.png' } as Express.Multer.File, - { originalname: 'image2.png' } as Express.Multer.File, - testBatman, - organization - ); - const images = await OrganizationsService.getOrganizationImages(orgId); - - expect(images).not.toBeNull(); - expect(images.applyInterestImage).toBe('uploaded-image1.png'); - expect(images.exploreAsGuestImage).toBe('uploaded-image2.png'); - }); - }); - describe('Update Application Link', () => { it('Fails if user is not admin', async () => { await expect( From 2344eb1efc0b4c405049fe2f2f90409ff4b4dc6a Mon Sep 17 00:00:00 2001 From: harish Date: Wed, 18 Dec 2024 11:34:11 -0500 Subject: [PATCH 27/40] #2725 images are side by side --- .../AdminToolsRecruitmentConfig.tsx | 127 ++++++++++-------- 1 file changed, 70 insertions(+), 57 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index 340503b253..cc2cd322a6 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -15,17 +15,11 @@ const AdminToolsRecruitmentConfig: React.FC = () => { const [addedImage1, setAddedImage1] = useState(undefined); const [addedImage2, setAddedImage2] = useState(undefined); + const [isUploadingApply, setIsUploadingApply] = useState(false); + const [isUploadingExplore, setIsUploadingExplore] = useState(false); - const { data: applyInterestImageUrl, isLoading: applyImageLoading } = useGetImageUrl( - organization?.applyInterestImageId ?? null - ); - const { data: exploreGuestImageUrl, isLoading: exploreImageLoading } = useGetImageUrl( - organization?.exploreAsGuestImageId ?? null - ); - - if (!organization || applyImageLoading || exploreImageLoading) { - return ; - } + const { data: applyInterestImageUrl } = useGetImageUrl(organization?.applyInterestImageId ?? null); + const { data: exploreGuestImageUrl } = useGetImageUrl(organization?.exploreAsGuestImageId ?? null); const handleFileUpload = async (files: File[], type: 'exploreAsGuest' | 'applyInterest') => { const validFiles: File[] = []; @@ -43,9 +37,12 @@ const AdminToolsRecruitmentConfig: React.FC = () => { if (validFiles.length > 0) { try { + type === 'applyInterest' ? setIsUploadingApply(true) : setIsUploadingExplore(true); await organizationImages(validFiles); } catch (error) { console.error('Error uploading images:', error); + } finally { + type === 'applyInterest' ? setIsUploadingApply(false) : setIsUploadingExplore(false); } } }; @@ -67,65 +64,81 @@ const AdminToolsRecruitmentConfig: React.FC = () => { Recruitment Images - + Apply Interest Image - {!addedImage1 && applyInterestImageUrl && ( - + {isUploadingApply ? ( + + + + ) : ( + <> + {!addedImage1 && applyInterestImageUrl && ( + + )} + { + if (e.target.files) { + setAddedImage1(e.target.files[0]); + } + }} + onSubmit={() => { + if (addedImage1) { + handleFileUpload([addedImage1], 'applyInterest'); + setAddedImage1(undefined); + } + }} + addedImage={addedImage1} + setAddedImage={setAddedImage1} + /> + )} - { - if (e.target.files) { - setAddedImage1(e.target.files[0]); - } - }} - onSubmit={() => { - if (addedImage1) { - handleFileUpload([addedImage1], 'applyInterest'); - setAddedImage1(undefined); - } - }} - addedImage={addedImage1} - setAddedImage={setAddedImage1} - /> Explore As Guest Image - {!addedImage2 && exploreGuestImageUrl && ( - + {isUploadingExplore ? ( + + + + ) : ( + <> + {!addedImage2 && exploreGuestImageUrl && ( + + )} + { + if (e.target.files) { + setAddedImage2(e.target.files[0]); + } + }} + onSubmit={() => { + if (addedImage2) { + handleFileUpload([addedImage2], 'exploreAsGuest'); + setAddedImage2(undefined); + } + }} + addedImage={addedImage2} + setAddedImage={setAddedImage2} + /> + )} - { - if (e.target.files) { - setAddedImage2(e.target.files[0]); - } - }} - onSubmit={() => { - if (addedImage2) { - handleFileUpload([addedImage2], 'exploreAsGuest'); - setAddedImage2(undefined); - } - }} - addedImage={addedImage2} - setAddedImage={setAddedImage2} - /> From 828196439ede62455cec9310f2e584c2317f9d12 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Fri, 20 Dec 2024 13:22:28 -0500 Subject: [PATCH 28/40] #2814 made checkboxes functional --- src/backend/src/routes/onboarding.routes.ts | 2 +- .../src/services/onboarding.services.ts | 22 ++++++- src/backend/tests/unmocked/onboarding.test.ts | 55 ++++++++++++++++ src/frontend/src/apis/onboarding.api.ts | 19 ++++++ src/frontend/src/hooks/onboarding.hook.ts | 38 ++++++++++- .../HomePage/components/SubtaskSection.tsx | 58 +++++++++++------ .../src/pages/HomePage/components/Task.tsx | 64 ++++++++++++++----- src/frontend/src/utils/urls.ts | 4 ++ 8 files changed, 221 insertions(+), 41 deletions(-) diff --git a/src/backend/src/routes/onboarding.routes.ts b/src/backend/src/routes/onboarding.routes.ts index 6a2670d383..fdbcb6a54c 100644 --- a/src/backend/src/routes/onboarding.routes.ts +++ b/src/backend/src/routes/onboarding.routes.ts @@ -42,7 +42,7 @@ onboardingRouter.post( onboardingRouter.post('/checklist/delete/:checklistId', OnboardingController.deleteChecklist); -onboardingRouter.post('/checklists/item/:checklistId/checked', OnboardingController.toggleChecklist); +onboardingRouter.post('/checklists/:checklistId/toggle', OnboardingController.toggleChecklist); onboardingRouter.get('/image/:fileId', OnboardingController.downloadImage); diff --git a/src/backend/src/services/onboarding.services.ts b/src/backend/src/services/onboarding.services.ts index 05920a19f9..c1f103defa 100644 --- a/src/backend/src/services/onboarding.services.ts +++ b/src/backend/src/services/onboarding.services.ts @@ -336,7 +336,27 @@ export default class OnboardingServices { const isChecked = checklist.usersChecked.some((user) => user.userId === userId); - if (checklist.parentChecklistId == null && checklist.subtasks.length > 0) { + if (checklist.parentChecklistId == null && isChecked) { + // Disconnect user from each child checklist + const childChecklists = await prisma.checklist.findMany({ + where: { parentChecklistId: checklistId } + }); + + await Promise.all( + childChecklists.map((checklist) => + prisma.checklist.update({ + where: { checklistId: checklist.checklistId }, + data: { + usersChecked: { + disconnect: { userId } + } + } + }) + ) + ); + } + + if (checklist.parentChecklistId == null && checklist.subtasks.length > 0 && !isChecked) { throw new HttpException(400, 'Cannot check off this checklist item because not all of its subtasks are checked.'); } diff --git a/src/backend/tests/unmocked/onboarding.test.ts b/src/backend/tests/unmocked/onboarding.test.ts index d258faabec..9cedab2777 100644 --- a/src/backend/tests/unmocked/onboarding.test.ts +++ b/src/backend/tests/unmocked/onboarding.test.ts @@ -584,6 +584,61 @@ describe('Onboarding tests', () => { expect(revertedParentItem?.usersChecked.length).toBe(0); }); + it('Unchecks all children when unchecking parent', async () => { + const batman = await createTestUser(batmanAppAdmin, orgId); + + const parentChecklist = await createTestChecklist(batman, orgId, 'Parent Checklist', undefined, undefined, undefined); + + const childChecklistItem1 = await createTestChecklist( + batman, + orgId, + 'Child Checklist Item 1', + undefined, + undefined, + parentChecklist.checklistId + ); + + const childChecklistItem2 = await createTestChecklist( + batman, + orgId, + 'Child Checklist Item 2', + undefined, + undefined, + parentChecklist.checklistId + ); + + await OnboardingServices.toggleChecklist(childChecklistItem1.checklistId, batman.userId); + await OnboardingServices.toggleChecklist(childChecklistItem2.checklistId, batman.userId); + + const updatedChildItem1 = await prisma.checklist.findUnique({ + where: { checklistId: childChecklistItem1.checklistId }, + include: { usersChecked: true } + }); + + const updatedChildItem2 = await prisma.checklist.findUnique({ + where: { checklistId: childChecklistItem2.checklistId }, + include: { usersChecked: true } + }); + + expect(updatedChildItem1?.usersChecked.length).toBe(1); + expect(updatedChildItem2?.usersChecked.length).toBe(1); + + await OnboardingServices.toggleChecklist(parentChecklist.checklistId, batman.userId); + + const revertedChildItem1 = await prisma.checklist.findUnique({ + where: { checklistId: childChecklistItem1.checklistId }, + include: { usersChecked: true } + }); + + const revertedChildItem2 = await prisma.checklist.findUnique({ + where: { checklistId: childChecklistItem2.checklistId }, + include: { usersChecked: true } + }); + + expect(revertedChildItem1?.usersChecked.length).toBe(0); + expect(revertedChildItem2?.usersChecked.length).toBe(0); + }); + it('throws NotFoundException when toggling a non-existing checklist item', async () => { const batman = await createTestUser(batmanAppAdmin, orgId); await expect(OnboardingServices.toggleChecklist('nonExistingId', batman.userId)).rejects.toThrow( diff --git a/src/frontend/src/apis/onboarding.api.ts b/src/frontend/src/apis/onboarding.api.ts index dff34e17a3..ded829330c 100644 --- a/src/frontend/src/apis/onboarding.api.ts +++ b/src/frontend/src/apis/onboarding.api.ts @@ -1,6 +1,7 @@ import { Checklist } from 'shared'; import { apiUrls } from '../utils/urls'; import axios from '../utils/axios'; +import { ToggleChecklistPayload } from '../hooks/onboarding.hook'; /** * API call to fetch all the checklists @@ -20,6 +21,15 @@ export const getGeneralChecklists = () => { }); }; +/** + * API call to fetch the checked checklists + */ +export const getCheckedChecklists = () => { + return axios.get(apiUrls.checkedChecklists(), { + transformResponse: (data) => JSON.parse(data) + }); +}; + /** * API call to fetch all the users checklists */ @@ -29,6 +39,15 @@ export const getUsersChecklists = () => { }); }; +/** + * API call to toggle a checklist + */ +export const toggleChecklist = (payload: ToggleChecklistPayload) => { + return axios.post(apiUrls.toggleChecklist(payload.checklistId), { + ...payload + }); +}; + /** * API Call to download a google image * @param fileId file id to be downloaded diff --git a/src/frontend/src/hooks/onboarding.hook.ts b/src/frontend/src/hooks/onboarding.hook.ts index 2194afa9ba..bdbfffba88 100644 --- a/src/frontend/src/hooks/onboarding.hook.ts +++ b/src/frontend/src/hooks/onboarding.hook.ts @@ -1,6 +1,17 @@ -import { useQuery } from 'react-query'; +import { useMutation, useQuery, useQueryClient } from 'react-query'; import { Checklist } from 'shared'; -import { getAllChecklists, getGeneralChecklists, getUsersChecklists, downloadGoogleImage } from '../apis/onboarding.api'; +import { + getAllChecklists, + getGeneralChecklists, + getUsersChecklists, + downloadGoogleImage, + toggleChecklist, + getCheckedChecklists +} from '../apis/onboarding.api'; + +export interface ToggleChecklistPayload { + checklistId: string; +} export const useAllChecklists = () => { return useQuery(['checklists'], async () => { @@ -16,6 +27,13 @@ export const useGeneralChecklists = () => { }); }; +export const useCheckedChecklists = () => { + return useQuery(['checklists', 'checked'], async () => { + const { data } = await getCheckedChecklists(); + return data; + }); +}; + export const useUsersTeamTypeChecklists = () => { return useQuery(['checklists', 'teamTypeChecklists'], async () => { const { data } = await getUsersChecklists(); @@ -23,6 +41,22 @@ export const useUsersTeamTypeChecklists = () => { }); }; +export const useToggleChecklist = () => { + const queryClient = useQueryClient(); + return useMutation( + ['checklists', 'edit'], + async (payload) => { + const { data } = await toggleChecklist(payload); + return data; + }, + { + onSuccess: () => { + queryClient.invalidateQueries(['checklists']); + } + } + ); +}; + export const useGetImageUrl = (imageFileId: string | null) => { return useQuery( ['image', imageFileId], diff --git a/src/frontend/src/pages/HomePage/components/SubtaskSection.tsx b/src/frontend/src/pages/HomePage/components/SubtaskSection.tsx index bbee35e79d..ba2851f19f 100644 --- a/src/frontend/src/pages/HomePage/components/SubtaskSection.tsx +++ b/src/frontend/src/pages/HomePage/components/SubtaskSection.tsx @@ -4,13 +4,28 @@ import { Box } from '@mui/system'; import React from 'react'; import { Checklist } from 'shared'; import { GridDragIcon } from '@mui/x-data-grid'; +import { useToggleChecklist } from '../../../hooks/onboarding.hook'; +import { useToast } from '../../../hooks/toasts.hooks'; -const SubtaskSection: React.FC<{ subtasks: Checklist[]; parentTask: Checklist; isAdmin?: boolean }> = ({ - subtasks, - parentTask, - isAdmin = false -}) => { +interface SubtaskSectionProps { + subtasks: Checklist[]; + parentTask: Checklist; + checkedChecklists: Checklist[]; + isAdmin?: boolean; +} + +const SubtaskSection: React.FC = ({ subtasks, parentTask, checkedChecklists, isAdmin = false }) => { const theme = useTheme(); + const toast = useToast(); + const { mutateAsync: toggleChecklist } = useToggleChecklist(); + + const handleToggleChecklist = async (subtaskId: string) => { + try { + await toggleChecklist({ checklistId: subtaskId }); + } catch (error: any) { + toast.error(error.message); + } + }; return ( ) : ( - + handleToggleChecklist(subtask.checklistId)}> + checklist.checklistId === subtask.checklistId)} + sx={{ + '& .MuiSvgIcon-root': { + fill: 'black', + backgroundColor: 'black', + borderRadius: 1 + }, + '&.Mui-checked .MuiSvgIcon-root': { + backgroundColor: 'white' + }, + '&:hover': { + backgroundColor: 'transparent' + } + }} + /> + )} {subtask.name} diff --git a/src/frontend/src/pages/HomePage/components/Task.tsx b/src/frontend/src/pages/HomePage/components/Task.tsx index 7634c9590f..df7d4f2c03 100644 --- a/src/frontend/src/pages/HomePage/components/Task.tsx +++ b/src/frontend/src/pages/HomePage/components/Task.tsx @@ -3,6 +3,10 @@ import { useState } from 'react'; import { KeyboardArrowRight, KeyboardArrowDown } from '@mui/icons-material'; import SubtaskSection from './SubtaskSection'; import { Checklist } from 'shared'; +import { useCheckedChecklists, useToggleChecklist } from '../../../hooks/onboarding.hook'; +import LoadingIndicator from '../../../components/LoadingIndicator'; +import ErrorPage from '../../ErrorPage'; +import { useToast } from '../../../hooks/toasts.hooks'; interface SubtaskProps { subtasks: Checklist[]; @@ -10,12 +14,36 @@ interface SubtaskProps { } const Task: React.FC = ({ subtasks, parentTask }) => { + const toast = useToast(); const [showSubtasks, setShowSubtasks] = useState(false); + const { mutateAsync: toggleChecklist } = useToggleChecklist(); + const { + data: checkedChecklists, + isLoading: checkedChecklistsLoading, + isError: checkedChecklistsIsError, + error: checkedChecklistsError + } = useCheckedChecklists(); const toggleShowSubtasks = () => { setShowSubtasks((prev) => !prev); }; + const handleToggleChecklist = async () => { + try { + await toggleChecklist({ checklistId: parentTask.checklistId }); + } catch (error: any) { + toast.error(error.message); + } + }; + + if (!checkedChecklists || checkedChecklistsLoading) return ; + + if (checkedChecklistsIsError) { + return ; + } + + const isParentTaskChecked = checkedChecklists.some((checklist) => checklist.checklistId === parentTask.checklistId); + return ( = ({ subtasks, parentTask }) => { }} > - - + + + {parentTask.name} {showSubtasks ? : } - {showSubtasks && } + {showSubtasks && } ); }; diff --git a/src/frontend/src/utils/urls.ts b/src/frontend/src/utils/urls.ts index c1490279b1..50fab96992 100644 --- a/src/frontend/src/utils/urls.ts +++ b/src/frontend/src/utils/urls.ts @@ -198,6 +198,8 @@ const faqDelete = (id: string) => `${recruitment()}/faq/${id}/delete`; const onboarding = () => `${API_URL}/onboarding`; const allChecklists = () => `${onboarding()}/checklists`; const generalChecklists = () => `${allChecklists()}/general`; +const checkedChecklists = () => `${allChecklists()}/checked`; +const toggleChecklist = (checklistId: string) => `${allChecklists()}/${checklistId}/toggle`; const usersTeamTypeChecklists = () => `${allChecklists()}/usersChecklists`; const imageById = (imageId: string) => `${onboarding()}/image/${imageId}`; @@ -361,6 +363,8 @@ export const apiUrls = { onboarding, allChecklists, generalChecklists, + checkedChecklists, + toggleChecklist, usersTeamTypeChecklists, imageById, From 534a23982167c394580080d2a355db88e31760d3 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Fri, 20 Dec 2024 14:05:43 -0500 Subject: [PATCH 29/40] #2814 tsc --- .../src/pages/HomePage/components/SubtaskSection.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/pages/HomePage/components/SubtaskSection.tsx b/src/frontend/src/pages/HomePage/components/SubtaskSection.tsx index ba2851f19f..4151ba85f7 100644 --- a/src/frontend/src/pages/HomePage/components/SubtaskSection.tsx +++ b/src/frontend/src/pages/HomePage/components/SubtaskSection.tsx @@ -10,7 +10,7 @@ import { useToast } from '../../../hooks/toasts.hooks'; interface SubtaskSectionProps { subtasks: Checklist[]; parentTask: Checklist; - checkedChecklists: Checklist[]; + checkedChecklists?: Checklist[]; isAdmin?: boolean; } @@ -27,6 +27,11 @@ const SubtaskSection: React.FC = ({ subtasks, parentTask, c } }; + const isChecklistChecked = (checklistId: string) => { + if (!checkedChecklists) return false; + return checkedChecklists.some((checklist) => checklist.checklistId === checklistId); + }; + return ( = ({ subtasks, parentTask, c ) : ( handleToggleChecklist(subtask.checklistId)}> checklist.checklistId === subtask.checklistId)} + checked={isChecklistChecked(subtask.checklistId)} sx={{ '& .MuiSvgIcon-root': { fill: 'black', From 2b409e69441c0f2afa9d55704a2d58897003ac85 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Sat, 21 Dec 2024 10:51:10 -0500 Subject: [PATCH 30/40] #2814 fixed naming --- src/frontend/src/pages/HomePage/components/Checklist.tsx | 4 ++-- .../HomePage/components/{Task.tsx => ParentTask.tsx} | 9 ++++----- .../src/pages/HomePage/components/SubtaskSection.tsx | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) rename src/frontend/src/pages/HomePage/components/{Task.tsx => ParentTask.tsx} (91%) diff --git a/src/frontend/src/pages/HomePage/components/Checklist.tsx b/src/frontend/src/pages/HomePage/components/Checklist.tsx index 40bb562b98..9044e2eb61 100644 --- a/src/frontend/src/pages/HomePage/components/Checklist.tsx +++ b/src/frontend/src/pages/HomePage/components/Checklist.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Checklist as ChecklistType } from 'shared'; import { Typography, Grid, Box, IconButton, useTheme } from '@mui/material'; import { KeyboardArrowRight, KeyboardArrowDown } from '@mui/icons-material'; -import Task from './Task'; +import ParentTask from './ParentTask'; const Checklist: React.FC<{ parentChecklists: ChecklistType[]; checklistName?: string }> = ({ parentChecklists, @@ -39,7 +39,7 @@ const Checklist: React.FC<{ parentChecklists: ChecklistType[]; checklistName?: s }} > {parentChecklists.map((parentChecklist) => ( - + ))} )} diff --git a/src/frontend/src/pages/HomePage/components/Task.tsx b/src/frontend/src/pages/HomePage/components/ParentTask.tsx similarity index 91% rename from src/frontend/src/pages/HomePage/components/Task.tsx rename to src/frontend/src/pages/HomePage/components/ParentTask.tsx index df7d4f2c03..a6814ad46d 100644 --- a/src/frontend/src/pages/HomePage/components/Task.tsx +++ b/src/frontend/src/pages/HomePage/components/ParentTask.tsx @@ -8,12 +8,11 @@ import LoadingIndicator from '../../../components/LoadingIndicator'; import ErrorPage from '../../ErrorPage'; import { useToast } from '../../../hooks/toasts.hooks'; -interface SubtaskProps { - subtasks: Checklist[]; +interface ParentTaskProps { parentTask: Checklist; } -const Task: React.FC = ({ subtasks, parentTask }) => { +const ParentTask: React.FC = ({ parentTask }) => { const toast = useToast(); const [showSubtasks, setShowSubtasks] = useState(false); const { mutateAsync: toggleChecklist } = useToggleChecklist(); @@ -82,9 +81,9 @@ const Task: React.FC = ({ subtasks, parentTask }) => { - {showSubtasks && } + {showSubtasks && } ); }; -export default Task; +export default ParentTask; diff --git a/src/frontend/src/pages/HomePage/components/SubtaskSection.tsx b/src/frontend/src/pages/HomePage/components/SubtaskSection.tsx index 4151ba85f7..b018f08d60 100644 --- a/src/frontend/src/pages/HomePage/components/SubtaskSection.tsx +++ b/src/frontend/src/pages/HomePage/components/SubtaskSection.tsx @@ -8,15 +8,15 @@ import { useToggleChecklist } from '../../../hooks/onboarding.hook'; import { useToast } from '../../../hooks/toasts.hooks'; interface SubtaskSectionProps { - subtasks: Checklist[]; parentTask: Checklist; checkedChecklists?: Checklist[]; isAdmin?: boolean; } -const SubtaskSection: React.FC = ({ subtasks, parentTask, checkedChecklists, isAdmin = false }) => { +const SubtaskSection: React.FC = ({ parentTask, checkedChecklists, isAdmin = false }) => { const theme = useTheme(); const toast = useToast(); + const { subtasks } = parentTask; const { mutateAsync: toggleChecklist } = useToggleChecklist(); const handleToggleChecklist = async (subtaskId: string) => { From 37fc7062d416f7664408414e0cfbb4babf04f8c2 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Sat, 21 Dec 2024 11:00:00 -0500 Subject: [PATCH 31/40] #2814 tsc --- .../pages/AdminToolsPage/OnboardingConfig/AdminChecklist.tsx | 2 +- .../src/pages/AdminToolsPage/OnboardingConfig/AdminTask.tsx | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/OnboardingConfig/AdminChecklist.tsx b/src/frontend/src/pages/AdminToolsPage/OnboardingConfig/AdminChecklist.tsx index 77e569b362..4c1c4751bf 100644 --- a/src/frontend/src/pages/AdminToolsPage/OnboardingConfig/AdminChecklist.tsx +++ b/src/frontend/src/pages/AdminToolsPage/OnboardingConfig/AdminChecklist.tsx @@ -57,7 +57,7 @@ export const AdminChecklist: React.FC<{ parentChecklists: Checklist[]; checklist }} > {parentChecklists.map((parentChecklist) => ( - + ))} diff --git a/src/frontend/src/pages/AdminToolsPage/OnboardingConfig/AdminTask.tsx b/src/frontend/src/pages/AdminToolsPage/OnboardingConfig/AdminTask.tsx index 25b5a8bd73..b50412c620 100644 --- a/src/frontend/src/pages/AdminToolsPage/OnboardingConfig/AdminTask.tsx +++ b/src/frontend/src/pages/AdminToolsPage/OnboardingConfig/AdminTask.tsx @@ -9,11 +9,10 @@ import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline'; import EditIcon from '@mui/icons-material/Edit'; interface AdminTaskProps { - subtasks: Checklist[]; parentTask: Checklist; } -const AdminTask: React.FC = ({ subtasks, parentTask }) => { +const AdminTask: React.FC = ({ parentTask }) => { const [showSubtasks, setShowSubtasks] = useState(false); const toggleShowSubtasks = () => { @@ -40,7 +39,7 @@ const AdminTask: React.FC = ({ subtasks, parentTask }) => { - {showSubtasks && } + {showSubtasks && } ); From f0d1b0f19807396dd8fb7fb3765c3c00b9066a3d Mon Sep 17 00:00:00 2001 From: harish Date: Sat, 21 Dec 2024 11:08:21 -0500 Subject: [PATCH 32/40] #2725 prettier --- src/frontend/src/hooks/organizations.hooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/hooks/organizations.hooks.ts b/src/frontend/src/hooks/organizations.hooks.ts index a3f01d4b93..8a7b25be32 100644 --- a/src/frontend/src/hooks/organizations.hooks.ts +++ b/src/frontend/src/hooks/organizations.hooks.ts @@ -6,7 +6,7 @@ import { getCurrentOrganization, setOrganizationImages, setOnboardingText, - updateOrganizationContacts + updateOrganizationContacts, updateApplicationLink } from '../apis/organizations.api'; From 62221ddb02aae2b411725151ebd733f933a3a7ab Mon Sep 17 00:00:00 2001 From: harish Date: Sat, 21 Dec 2024 11:17:19 -0500 Subject: [PATCH 33/40] #2725 all tests pass??? --- .../AdminToolsRecruitmentConfig.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index cc2cd322a6..5bb848dc0a 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -3,15 +3,28 @@ import MilestoneTable from './MilestoneTable'; import FAQsTable from './FAQTable'; import { useToast } from '../../../hooks/toasts.hooks'; import NERUploadButton from '../../../components/NERUploadButton'; -import { useState } from 'react'; +import React, { useState } from 'react'; import { useCurrentOrganization, useSetOrganizationImages } from '../../../hooks/organizations.hooks'; import LoadingIndicator from '../../../components/LoadingIndicator'; import { useGetImageUrl } from '../../../hooks/onboarding.hooks'; +import ErrorPage from '../../ErrorPage'; const AdminToolsRecruitmentConfig: React.FC = () => { const { mutateAsync: organizationImages } = useSetOrganizationImages(); const toast = useToast(); - const { data: organization } = useCurrentOrganization(); + const { + data: organization, + isLoading: organizationIsLoading, + isError: organizationIsError, + error: organizationError + } = useCurrentOrganization(); + + if (!organization || organizationIsLoading) { + return ; + } + if (organizationIsError) { + return ; + } const [addedImage1, setAddedImage1] = useState(undefined); const [addedImage2, setAddedImage2] = useState(undefined); From b064eb5831c31bf69cc7579770a514041928a179 Mon Sep 17 00:00:00 2001 From: harish Date: Sat, 21 Dec 2024 11:23:14 -0500 Subject: [PATCH 34/40] #2725 random commit --- .../AdminToolsRecruitmentConfig.tsx | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index 5bb848dc0a..230b0bf840 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -12,28 +12,16 @@ import ErrorPage from '../../ErrorPage'; const AdminToolsRecruitmentConfig: React.FC = () => { const { mutateAsync: organizationImages } = useSetOrganizationImages(); const toast = useToast(); - const { - data: organization, - isLoading: organizationIsLoading, - isError: organizationIsError, - error: organizationError - } = useCurrentOrganization(); + const { data: organization } = useCurrentOrganization(); - if (!organization || organizationIsLoading) { - return ; - } - if (organizationIsError) { - return ; - } + const { data: applyInterestImageUrl } = useGetImageUrl(organization?.applyInterestImageId ?? null); + const { data: exploreGuestImageUrl } = useGetImageUrl(organization?.exploreAsGuestImageId ?? null); const [addedImage1, setAddedImage1] = useState(undefined); const [addedImage2, setAddedImage2] = useState(undefined); const [isUploadingApply, setIsUploadingApply] = useState(false); const [isUploadingExplore, setIsUploadingExplore] = useState(false); - const { data: applyInterestImageUrl } = useGetImageUrl(organization?.applyInterestImageId ?? null); - const { data: exploreGuestImageUrl } = useGetImageUrl(organization?.exploreAsGuestImageId ?? null); - const handleFileUpload = async (files: File[], type: 'exploreAsGuest' | 'applyInterest') => { const validFiles: File[] = []; files.forEach((file) => { From bd515bcb8bff701c79503f221e9b841904488871 Mon Sep 17 00:00:00 2001 From: harish Date: Sat, 21 Dec 2024 11:25:15 -0500 Subject: [PATCH 35/40] #2725 linting --- .../RecruitmentConfig/AdminToolsRecruitmentConfig.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index 230b0bf840..041de49306 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -7,7 +7,6 @@ import React, { useState } from 'react'; import { useCurrentOrganization, useSetOrganizationImages } from '../../../hooks/organizations.hooks'; import LoadingIndicator from '../../../components/LoadingIndicator'; import { useGetImageUrl } from '../../../hooks/onboarding.hooks'; -import ErrorPage from '../../ErrorPage'; const AdminToolsRecruitmentConfig: React.FC = () => { const { mutateAsync: organizationImages } = useSetOrganizationImages(); From 276373c107ae34b3c56f2afb1b818154d6dc5a47 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Sun, 22 Dec 2024 10:29:33 -0500 Subject: [PATCH 36/40] #2814 unchecks all children checklists --- src/backend/src/services/onboarding.services.ts | 17 +++++++++++++++++ src/backend/tests/unmocked/onboarding.test.ts | 6 +++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/backend/src/services/onboarding.services.ts b/src/backend/src/services/onboarding.services.ts index 125439647b..b00b994365 100644 --- a/src/backend/src/services/onboarding.services.ts +++ b/src/backend/src/services/onboarding.services.ts @@ -351,6 +351,23 @@ export default class OnboardingServices { } if (isChecked) { + const childChecklists = await prisma.checklist.findMany({ + where: { parentChecklistId: checklistId } + }); + + await Promise.all( + childChecklists.map((checklist) => + prisma.checklist.update({ + where: { checklistId: checklist.checklistId }, + data: { + usersChecked: { + disconnect: { userId } + } + } + }) + ) + ); + await prisma.checklist.update({ where: { checklistId }, data: { diff --git a/src/backend/tests/unmocked/onboarding.test.ts b/src/backend/tests/unmocked/onboarding.test.ts index 0b971daca0..40f7c0fc44 100644 --- a/src/backend/tests/unmocked/onboarding.test.ts +++ b/src/backend/tests/unmocked/onboarding.test.ts @@ -607,8 +607,8 @@ describe('Onboarding tests', () => { parentChecklist.checklistId ); - await OnboardingServices.toggleChecklist(childChecklistItem1.checklistId, batman.userId); - await OnboardingServices.toggleChecklist(childChecklistItem2.checklistId, batman.userId); + await OnboardingServices.toggleChecklist(childChecklistItem1.checklistId, batman, organization); + await OnboardingServices.toggleChecklist(childChecklistItem2.checklistId, batman, organization); const updatedChildItem1 = await prisma.checklist.findUnique({ where: { checklistId: childChecklistItem1.checklistId }, @@ -623,7 +623,7 @@ describe('Onboarding tests', () => { expect(updatedChildItem1?.usersChecked.length).toBe(1); expect(updatedChildItem2?.usersChecked.length).toBe(1); - await OnboardingServices.toggleChecklist(parentChecklist.checklistId, batman.userId); + await OnboardingServices.toggleChecklist(parentChecklist.checklistId, batman, organization); const revertedChildItem1 = await prisma.checklist.findUnique({ where: { checklistId: childChecklistItem1.checklistId }, From f865f7d1a950d00f5b10ff1215c726bb563154e8 Mon Sep 17 00:00:00 2001 From: aaryan1203 Date: Sun, 22 Dec 2024 10:33:20 -0500 Subject: [PATCH 37/40] #2814 tests --- src/backend/tests/unmocked/organization.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/backend/tests/unmocked/organization.test.ts b/src/backend/tests/unmocked/organization.test.ts index bfbc552cdb..c37940867a 100644 --- a/src/backend/tests/unmocked/organization.test.ts +++ b/src/backend/tests/unmocked/organization.test.ts @@ -268,7 +268,7 @@ describe('Organization Tests', () => { const testBatman = await createTestUser(batmanAppAdmin, orgId); const testSuperman = await createTestUser(supermanAdmin, orgId); - const updatedOrganization = await OrganizationsService.updateOrganizationContacts(testBatman, organization, [ + await OrganizationsService.updateOrganizationContacts(testBatman, organization, [ { userId: testBatman.userId, title: 'Chief Software Engineer' }, { userId: testSuperman.userId, title: 'Chief Mechanical Engineer' } ]); @@ -282,10 +282,6 @@ describe('Organization Tests', () => { expect(allContacts.length).toBe(2); expect(allContacts.some((contact) => contact.userId === testBatman.userId)).toBeTruthy(); expect(allContacts.some((contact) => contact.userId === testSuperman.userId)).toBeTruthy(); - - expect(updatedOrganization).not.toBeNull(); - expect(updatedOrganization.contacts[0].title).toBe('Chief Software Engineer'); - expect(updatedOrganization.contacts[1].title).toBe('Chief Mechanical Engineer'); }); }); }); From 3ff41123cb8dee032df9567304704edb7b4a39ce Mon Sep 17 00:00:00 2001 From: harish Date: Sun, 22 Dec 2024 13:33:12 -0500 Subject: [PATCH 38/40] #2725 will linting pass? --- .../AdminToolsRecruitmentConfig.tsx | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index 041de49306..d676302751 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -7,14 +7,33 @@ import React, { useState } from 'react'; import { useCurrentOrganization, useSetOrganizationImages } from '../../../hooks/organizations.hooks'; import LoadingIndicator from '../../../components/LoadingIndicator'; import { useGetImageUrl } from '../../../hooks/onboarding.hooks'; +import ErrorPage from '../../ErrorPage'; const AdminToolsRecruitmentConfig: React.FC = () => { - const { mutateAsync: organizationImages } = useSetOrganizationImages(); + const { + mutateAsync: organizationImages, + isLoading: organizationImagesIsLoading, + isError: organizationImagesIsError, + error: organizationImagesError + } = useSetOrganizationImages(); + const toast = useToast(); - const { data: organization } = useCurrentOrganization(); - const { data: applyInterestImageUrl } = useGetImageUrl(organization?.applyInterestImageId ?? null); - const { data: exploreGuestImageUrl } = useGetImageUrl(organization?.exploreAsGuestImageId ?? null); + const { + data: organization, + isLoading: organizationIsLoading, + isError: organizationIsError, + error: organizationError + } = useCurrentOrganization(); + + if (organizationIsError) { + return ; + } + + if (organizationImagesIsLoading || !organization || organizationIsLoading) return ; + + const { data: applyInterestImageUrl } = useGetImageUrl(organization.applyInterestImageId); + const { data: exploreGuestImageUrl } = useGetImageUrl(organization.exploreAsGuestImageId); const [addedImage1, setAddedImage1] = useState(undefined); const [addedImage2, setAddedImage2] = useState(undefined); @@ -39,8 +58,17 @@ const AdminToolsRecruitmentConfig: React.FC = () => { try { type === 'applyInterest' ? setIsUploadingApply(true) : setIsUploadingExplore(true); await organizationImages(validFiles); - } catch (error) { + toast.success('Image uploaded successfully!'); + } catch (error: any) { console.error('Error uploading images:', error); + + // Check if the error is from organizationImagesError + if (organizationImagesIsError && organizationImagesError instanceof Error) { + toast.error(organizationImagesError.message, 5000); + } else { + // Default fallback for unexpected errors + toast.error('An unexpected error occurred during upload.', 5000); + } } finally { type === 'applyInterest' ? setIsUploadingApply(false) : setIsUploadingExplore(false); } From f2962ad89451f45c89bb7991dc7eb0f1937a05d9 Mon Sep 17 00:00:00 2001 From: harish Date: Sun, 22 Dec 2024 13:37:26 -0500 Subject: [PATCH 39/40] #2725 not using hooks conditionally --- .../AdminToolsRecruitmentConfig.tsx | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index d676302751..56b8891d3e 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -26,20 +26,20 @@ const AdminToolsRecruitmentConfig: React.FC = () => { error: organizationError } = useCurrentOrganization(); - if (organizationIsError) { - return ; - } - - if (organizationImagesIsLoading || !organization || organizationIsLoading) return ; - - const { data: applyInterestImageUrl } = useGetImageUrl(organization.applyInterestImageId); - const { data: exploreGuestImageUrl } = useGetImageUrl(organization.exploreAsGuestImageId); + const { data: applyInterestImageUrl } = useGetImageUrl(organization?.applyInterestImageId ?? null); + const { data: exploreGuestImageUrl } = useGetImageUrl(organization?.exploreAsGuestImageId ?? null); const [addedImage1, setAddedImage1] = useState(undefined); const [addedImage2, setAddedImage2] = useState(undefined); const [isUploadingApply, setIsUploadingApply] = useState(false); const [isUploadingExplore, setIsUploadingExplore] = useState(false); + if (organizationIsError) { + return ; + } + + if (organizationImagesIsLoading || !organization || organizationIsLoading) return ; + const handleFileUpload = async (files: File[], type: 'exploreAsGuest' | 'applyInterest') => { const validFiles: File[] = []; files.forEach((file) => { @@ -60,13 +60,9 @@ const AdminToolsRecruitmentConfig: React.FC = () => { await organizationImages(validFiles); toast.success('Image uploaded successfully!'); } catch (error: any) { - console.error('Error uploading images:', error); - - // Check if the error is from organizationImagesError if (organizationImagesIsError && organizationImagesError instanceof Error) { toast.error(organizationImagesError.message, 5000); } else { - // Default fallback for unexpected errors toast.error('An unexpected error occurred during upload.', 5000); } } finally { From b74cc8938e802885b5f175a7767d25f88f0b4205 Mon Sep 17 00:00:00 2001 From: harish Date: Sun, 22 Dec 2024 13:50:31 -0500 Subject: [PATCH 40/40] #2725 error and loading indicator stuff --- .../AdminToolsRecruitmentConfig.tsx | 4 +-- .../src/pages/HomePage/GuestHomePage.tsx | 26 +++++++++++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx index 56b8891d3e..202baf67a8 100644 --- a/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx @@ -61,9 +61,7 @@ const AdminToolsRecruitmentConfig: React.FC = () => { toast.success('Image uploaded successfully!'); } catch (error: any) { if (organizationImagesIsError && organizationImagesError instanceof Error) { - toast.error(organizationImagesError.message, 5000); - } else { - toast.error('An unexpected error occurred during upload.', 5000); + toast.error(organizationImagesError.message); } } finally { type === 'applyInterest' ? setIsUploadingApply(false) : setIsUploadingExplore(false); diff --git a/src/frontend/src/pages/HomePage/GuestHomePage.tsx b/src/frontend/src/pages/HomePage/GuestHomePage.tsx index 4de4be5197..300ec8a2ce 100644 --- a/src/frontend/src/pages/HomePage/GuestHomePage.tsx +++ b/src/frontend/src/pages/HomePage/GuestHomePage.tsx @@ -14,29 +14,39 @@ import { useGetImageUrl } from '../../hooks/onboarding.hooks'; const GuestHomePage = () => { const user = useCurrentUser(); const history = useHistory(); - const { data: organization, isError, error, isLoading } = useCurrentOrganization(); + const { + data: organization, + isLoading: organizationIsLoading, + isError: organizationIsError, + error: organizationError + } = useCurrentOrganization(); const { setCurrentHomePage } = useHomePageContext(); const { data: applyInterestImageUrl, isLoading: applyImageLoading, - isError: applyImageError + isError: applyImageIsError, + error: applyImageError } = useGetImageUrl(organization?.applyInterestImageId ?? null); const { data: exploreGuestImageUrl, isLoading: exploreImageLoading, - isError: exploreImageError + isError: exploreImageIsError, + error: exploreImageError } = useGetImageUrl(organization?.exploreAsGuestImageId ?? null); useEffect(() => { setCurrentHomePage('guest'); }, [setCurrentHomePage]); - if (!organization || isLoading || applyImageLoading || exploreImageLoading) return ; - if (isError) return ; - if (applyImageError) return ; - if (exploreImageError) return ; - if (!applyInterestImageUrl || !exploreGuestImageUrl) throw new Error('Image URLs are undefined'); + if (organizationIsError) { + return ; + } + if (applyImageIsError) return ; + if (exploreImageIsError) return ; + + if (!organization || organizationIsLoading || applyImageLoading || exploreImageLoading) return ; + if (!applyInterestImageUrl || !exploreGuestImageUrl) return ; return (