Skip to content

Commit 7bb8a6e

Browse files
committed
#3873 edit platform description endpoints + frontend in recruitment admin tools
1 parent 0031382 commit 7bb8a6e

9 files changed

Lines changed: 137 additions & 2 deletions

File tree

src/backend/src/controllers/organizations.controllers.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,20 @@ export default class OrganizationsController {
173173
}
174174
}
175175

176+
static async setPlatformDescription(req: Request, res: Response, next: NextFunction) {
177+
try {
178+
const updatedOrg = await OrganizationsService.setPlatformDescription(
179+
req.body.platformDescription,
180+
req.currentUser,
181+
req.organization
182+
);
183+
184+
res.status(200).json(updatedOrg);
185+
} catch (error: unknown) {
186+
next(error);
187+
}
188+
}
189+
176190
static async getOrganizationFeaturedProjects(req: Request, res: Response, next: NextFunction) {
177191
try {
178192
const featuredProjects = await OrganizationsService.getOrganizationFeaturedProjects(req.organization.organizationId);

src/backend/src/routes/organizations.routes.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ organizationRouter.post(
6363
validateInputs,
6464
OrganizationsController.setOrganizationDescription
6565
);
66+
organizationRouter.post(
67+
'/platform-description/set',
68+
body('platformDescription').isString(),
69+
validateInputs,
70+
OrganizationsController.setPlatformDescription
71+
);
6672
organizationRouter.get('/featured-projects', OrganizationsController.getOrganizationFeaturedProjects);
6773
organizationRouter.post(
6874
'/workspaceId/set',

src/backend/src/services/organizations.services.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,23 @@ export default class OrganizationsService {
378378
return updatedOrg;
379379
}
380380

381+
/**
382+
* Sets the platform description of a given organization.
383+
* @param platformDescription the new platform description
384+
* @param submitter the user making the change
385+
* @param organization the organization whose platform description is changing
386+
* @throws if the user is not an admin
387+
*/
388+
static async setPlatformDescription(platformDescription: string, submitter: User, organization: Organization) {
389+
if (!(await userHasPermission(submitter.userId, organization.organizationId, isAdmin))) {
390+
throw new AccessDeniedAdminOnlyException('set platform description');
391+
}
392+
return prisma.organization.update({
393+
where: { organizationId: organization.organizationId },
394+
data: { platformDescription }
395+
});
396+
}
397+
381398
/**
382399
* Gets the featured projects for the given organization Id
383400
* @param organizationId the organization to get the projects for

src/backend/src/transformers/organizationTransformer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const organizationTransformer = (organization: Organization): Organizatio
66
...organization,
77
applicationLink: organization.applicationLink ?? undefined,
88
newMemberImageId: organization.newMemberImageId ?? undefined,
9+
platformDescription: organization.platformDescription ?? '',
910
platformLogoImageId: organization.platformLogoImageId ?? undefined
1011
};
1112
};

src/frontend/src/apis/organizations.api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ export const setOrganizationDescription = async (description: string) => {
4242
});
4343
};
4444

45+
export const setPlatformDescription = async (platformDescription: string) => {
46+
return axios.post<Organization>(apiUrls.organizationsSetPlatformDescription(), {
47+
platformDescription
48+
});
49+
};
50+
4551
export const getOrganizationLogo = async () => {
4652
return axios.get<string>(apiUrls.organizationsLogoImage(), {
4753
transformResponse: (data) => JSON.parse(data)

src/frontend/src/hooks/organizations.hooks.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getFeaturedProjects,
77
getCurrentOrganization,
88
setOrganizationDescription,
9+
setPlatformDescription,
910
setOrganizationFeaturedProjects,
1011
setOrganizationWorkspaceId,
1112
setOrganizationLogo,
@@ -149,6 +150,22 @@ export const useSetOrganizationDescription = () => {
149150
);
150151
};
151152

153+
export const useSetPlatformDescription = () => {
154+
const queryClient = useQueryClient();
155+
return useMutation<Organization, Error, string>(
156+
['organizations', 'platform-description'],
157+
async (platformDescription: string) => {
158+
const { data } = await setPlatformDescription(platformDescription);
159+
return data;
160+
},
161+
{
162+
onSuccess: () => {
163+
queryClient.invalidateQueries(['organizations']);
164+
}
165+
}
166+
);
167+
};
168+
152169
export const useSetFeaturedProjects = () => {
153170
const queryClient = useQueryClient();
154171
return useMutation<Organization, Error, ProjectPreview[]>(

src/frontend/src/pages/AdminToolsPage/RecruitmentConfig/AdminToolsRecruitmentConfig.tsx

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
1-
import { Box, Grid, Typography } from '@mui/material';
1+
import { Box, FormControl, Grid, Typography } from '@mui/material';
22
import MilestoneTable from './MilestoneTable';
33
import FAQsTable from './FAQTable';
44
import React, { useState } from 'react';
5-
import { useCurrentOrganization, useSetOrganizationPlatformLogoImage } from '../../../hooks/organizations.hooks';
5+
import { useForm } from 'react-hook-form';
6+
import { yupResolver } from '@hookform/resolvers/yup';
7+
import * as yup from 'yup';
8+
import {
9+
useCurrentOrganization,
10+
useSetOrganizationPlatformLogoImage,
11+
useSetPlatformDescription
12+
} from '../../../hooks/organizations.hooks';
613
import LoadingIndicator from '../../../components/LoadingIndicator';
714
import ErrorPage from '../../ErrorPage';
815
import ApplicationLinkTable from './ApplicationLinkTable';
916
import { useGetImageUrl } from '../../../hooks/onboarding.hook';
1017
import NERUploadButton from '../../../components/NERUploadButton';
18+
import NERSuccessButton from '../../../components/NERSuccessButton';
19+
import ReactHookTextField from '../../../components/ReactHookTextField';
1120
import { useToast } from '../../../hooks/toasts.hooks';
1221
import { MAX_FILE_SIZE } from 'shared';
1322

23+
const platformDescriptionSchema = yup.object().shape({
24+
platformDescription: yup.string().required()
25+
});
26+
1427
const AdminToolsRecruitmentConfig: React.FC = () => {
1528
const {
1629
data: organization,
@@ -20,13 +33,30 @@ const AdminToolsRecruitmentConfig: React.FC = () => {
2033
} = useCurrentOrganization();
2134

2235
const { mutateAsync: setPlatformLogoImage, isLoading: platformLogoLoading } = useSetOrganizationPlatformLogoImage();
36+
const { mutateAsync: setPlatformDescriptionMutation, isLoading: platformDescriptionSaving } = useSetPlatformDescription();
2337

2438
const { data: platformLogoImageUrl } = useGetImageUrl(organization?.platformLogoImageId ?? null);
2539

2640
const toast = useToast();
2741

2842
const [addedPlatformLogo, setAddedPlatformLogo] = useState<File | undefined>(undefined);
2943

44+
const { control, handleSubmit, reset } = useForm<{ platformDescription: string }>({
45+
resolver: yupResolver(platformDescriptionSchema),
46+
defaultValues: { platformDescription: organization?.platformDescription ?? '' }
47+
});
48+
const formKey = organization?.organizationId ?? 'loading';
49+
50+
const onPlatformDescriptionSubmit = async (data: { platformDescription: string }) => {
51+
try {
52+
const updated = await setPlatformDescriptionMutation(data.platformDescription);
53+
reset({ platformDescription: updated.platformDescription });
54+
toast.success('Platform description saved.');
55+
} catch (e) {
56+
toast.error(e instanceof Error ? e.message : 'Failed to save platform description');
57+
}
58+
};
59+
3060
const handlePlatformLogoUpload = async () => {
3161
if (!addedPlatformLogo) return;
3262
if (addedPlatformLogo.size >= MAX_FILE_SIZE) {
@@ -99,6 +129,47 @@ const AdminToolsRecruitmentConfig: React.FC = () => {
99129
</Box>
100130
)}
101131
</Grid>
132+
<Grid item xs={12} md={6}>
133+
<Typography variant="h5" gutterBottom borderBottom={1} color="#ef4345" borderColor="white">
134+
Platform Description
135+
</Typography>
136+
<form
137+
id="platform-description-form"
138+
key={formKey}
139+
onSubmit={(e) => {
140+
e.preventDefault();
141+
e.stopPropagation();
142+
handleSubmit(onPlatformDescriptionSubmit)(e);
143+
}}
144+
onKeyPress={(e) => {
145+
e.key === 'Enter' && e.preventDefault();
146+
}}
147+
>
148+
<FormControl sx={{ width: '100%' }}>
149+
<ReactHookTextField
150+
name="platformDescription"
151+
control={control}
152+
multiline
153+
rows={5}
154+
fullWidth
155+
required
156+
placeholder="Enter platform description for the guest home page..."
157+
sx={{ mb: 1 }}
158+
/>
159+
</FormControl>
160+
<Box sx={{ display: 'flex', justifyContent: 'end' }}>
161+
<NERSuccessButton
162+
type="submit"
163+
form="platform-description-form"
164+
variant="contained"
165+
disabled={platformDescriptionSaving}
166+
sx={{ mt: 1 }}
167+
>
168+
{platformDescriptionSaving ? 'Saving...' : 'Save'}
169+
</NERSuccessButton>
170+
</Box>
171+
</form>
172+
</Grid>
102173
</Grid>
103174
</Box>
104175
);

src/frontend/src/utils/urls.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ const organizationsUpdateContacts = () => `${organizations()}/contacts/set`;
362362
const organizationsSetOnboardingText = () => `${organizations()}/onboardingText/set`;
363363
const organizationsUpdateApplicationLink = () => `${organizations()}/application-link/update`;
364364
const organizationsSetDescription = () => `${organizations()}/description/set`;
365+
const organizationsSetPlatformDescription = () => `${organizations()}/platform-description/set`;
365366
const organizationsFeaturedProjects = () => `${organizations()}/featured-projects`;
366367
const organizationsLogoImage = () => `${organizations()}/logo`;
367368
const organizationsSetLogoImage = () => `${organizations()}/logo/update`;
@@ -725,6 +726,7 @@ export const apiUrls = {
725726
organizationsUpdateApplicationLink,
726727
organizationsFeaturedProjects,
727728
organizationsSetDescription,
729+
organizationsSetPlatformDescription,
728730
organizationsLogoImage,
729731
organizationsSetLogoImage,
730732
organizationsNewMemberImage,

src/shared/src/types/user-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export type OrganizationPreview = Pick<
4949
| 'description'
5050
| 'applicationLink'
5151
| 'newMemberImageId'
52+
| 'platformDescription'
5253
| 'platformLogoImageId'
5354
>;
5455

0 commit comments

Comments
 (0)