diff --git a/src/frontend/src/pages/AdminToolsPage/EditGuestView/CreateGuestDefinitionFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/EditGuestView/CreateGuestDefinitionFormModal.tsx
new file mode 100644
index 0000000000..e4e6ba6e11
--- /dev/null
+++ b/src/frontend/src/pages/AdminToolsPage/EditGuestView/CreateGuestDefinitionFormModal.tsx
@@ -0,0 +1,22 @@
+import ErrorPage from '../../ErrorPage';
+import LoadingIndicator from '../../../components/LoadingIndicator';
+import { useCreateGuestDefinition } from '../../../hooks/recruitment.hooks';
+import { GuestDefinitionType } from 'shared';
+import GuestDefinitionFormModal from './GuestDefinitionFormModal';
+
+interface CreateGuestDefinitionFormModalProps {
+ open: boolean;
+ handleClose: () => void;
+ type: GuestDefinitionType;
+}
+
+const CreateGuestDefinitionFormModal = ({ open, handleClose, type }: CreateGuestDefinitionFormModalProps) => {
+ const { isLoading, isError, error, mutateAsync } = useCreateGuestDefinition();
+
+ if (isError) return ;
+ if (isLoading) return ;
+
+ return ;
+};
+
+export default CreateGuestDefinitionFormModal;
diff --git a/src/frontend/src/pages/AdminToolsPage/EditGuestView/EditGuestDefinitionFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/EditGuestView/EditGuestDefinitionFormModal.tsx
new file mode 100644
index 0000000000..f10c133d82
--- /dev/null
+++ b/src/frontend/src/pages/AdminToolsPage/EditGuestView/EditGuestDefinitionFormModal.tsx
@@ -0,0 +1,30 @@
+import ErrorPage from '../../ErrorPage';
+import LoadingIndicator from '../../../components/LoadingIndicator';
+import { useEditGuestDefinitions } from '../../../hooks/recruitment.hooks';
+import { GuestDefinition } from 'shared';
+import GuestDefinitionFormModal from './GuestDefinitionFormModal';
+
+interface EditGuestDefinitionFormModalProps {
+ open: boolean;
+ handleClose: () => void;
+ definition: GuestDefinition;
+}
+
+const EditGuestDefinitionFormModal = ({ open, handleClose, definition }: EditGuestDefinitionFormModalProps) => {
+ const { isLoading, isError, error, mutateAsync } = useEditGuestDefinitions(definition.definitionId);
+
+ if (isError) return ;
+ if (isLoading) return ;
+
+ return (
+
+ );
+};
+
+export default EditGuestDefinitionFormModal;
diff --git a/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestDefinitionFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestDefinitionFormModal.tsx
new file mode 100644
index 0000000000..2cde85f9fc
--- /dev/null
+++ b/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestDefinitionFormModal.tsx
@@ -0,0 +1,121 @@
+import { useForm } from 'react-hook-form';
+import NERFormModal from '../../../components/NERFormModal';
+import { FormControl, FormLabel, FormHelperText } from '@mui/material';
+import ReactHookTextField from '../../../components/ReactHookTextField';
+import * as yup from 'yup';
+import { yupResolver } from '@hookform/resolvers/yup';
+import useFormPersist from 'react-hook-form-persist';
+import { FormStorageKey } from '../../../utils/form';
+import { GuestDefinitionPayload } from '../../../hooks/recruitment.hooks';
+import { GuestDefinition, GuestDefinitionType } from 'shared';
+
+interface GuestDefinitionFormModalProps {
+ open: boolean;
+ handleClose: () => void;
+ type: GuestDefinitionType;
+ defaultValues?: GuestDefinition;
+ onSubmit: (data: GuestDefinitionPayload) => Promise;
+}
+
+const schema = yup.object().shape({
+ term: yup.string().required('Term is required'),
+ description: yup.string().required('Description is required'),
+ order: yup.number().required('Order is required').min(0, 'Order must be non-negative'),
+ icon: yup.string().optional(),
+ buttonText: yup.string().optional(),
+ buttonLink: yup.string().optional()
+});
+
+const GuestDefinitionFormModal: React.FC = ({
+ open,
+ handleClose,
+ type,
+ defaultValues,
+ onSubmit
+}) => {
+ const onFormSubmit = async (data: Omit) => {
+ await onSubmit({
+ ...data,
+ type,
+ icon: data.icon || undefined,
+ buttonText: data.buttonText || undefined,
+ buttonLink: data.buttonLink || undefined
+ });
+ handleClose();
+ };
+
+ const {
+ handleSubmit,
+ control,
+ reset,
+ formState: { errors },
+ watch,
+ setValue
+ } = useForm({
+ resolver: yupResolver(schema),
+ defaultValues: {
+ term: defaultValues?.term ?? '',
+ description: defaultValues?.description ?? '',
+ order: defaultValues?.order ?? 0,
+ icon: defaultValues?.icon ?? '',
+ buttonText: defaultValues?.buttonText ?? '',
+ buttonLink: defaultValues?.buttonLink ?? ''
+ }
+ });
+
+ const formStorageKey = defaultValues ? FormStorageKey.EDIT_GUEST_DEFINITION : FormStorageKey.CREATE_GUEST_DEFINITION;
+
+ useFormPersist(formStorageKey, { watch, setValue });
+
+ const handleCancel = () => {
+ reset({ term: '', description: '', order: 0, icon: '', buttonText: '', buttonLink: '' });
+ sessionStorage.removeItem(formStorageKey);
+ handleClose();
+ };
+
+ return (
+ reset({ term: '', description: '', order: 0, icon: '', buttonText: '', buttonLink: '' })}
+ handleUseFormSubmit={handleSubmit}
+ onFormSubmit={onFormSubmit}
+ formId="guest-definition-form"
+ showCloseButton
+ >
+
+ Term*
+
+ {errors.term?.message}
+
+
+ Description*
+
+ {errors.description?.message}
+
+
+ Order*
+
+ {errors.order?.message}
+
+
+ Icon
+
+ {errors.icon?.message}
+
+
+ Button Text
+
+ {errors.buttonText?.message}
+
+
+ Button Link
+
+ {errors.buttonLink?.message}
+
+
+ );
+};
+
+export default GuestDefinitionFormModal;
diff --git a/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestDefinitionsTable.tsx b/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestDefinitionsTable.tsx
new file mode 100644
index 0000000000..2208da7c1b
--- /dev/null
+++ b/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestDefinitionsTable.tsx
@@ -0,0 +1,137 @@
+import React, { useState } from 'react';
+import { TableRow, TableCell, Box, Table as MuiTable, TableHead, TableBody, Typography, Button } from '@mui/material';
+import EditIcon from '@mui/icons-material/Edit';
+import DeleteIcon from '@mui/icons-material/Delete';
+import { GuestDefinition, GuestDefinitionType } from 'shared';
+import { NERButton } from '../../../components/NERButton';
+import { useAllGuestDefinitions, useDeleteGuestDefinition } from '../../../hooks/recruitment.hooks';
+import LoadingIndicator from '../../../components/LoadingIndicator';
+import { useHistoryState } from '../../../hooks/misc.hooks';
+import ErrorPage from '../../ErrorPage';
+import NERDeleteModal from '../../../components/NERDeleteModal';
+import { useToast } from '../../../hooks/toasts.hooks';
+import CreateGuestDefinitionFormModal from './CreateGuestDefinitionFormModal';
+import EditGuestDefinitionFormModal from './EditGuestDefinitionFormModal';
+
+interface GuestDefinitionsTableProps {
+ type: GuestDefinitionType;
+}
+
+const GuestDefinitionsTable = ({ type }: GuestDefinitionsTableProps) => {
+ const [createModalShow, setCreateModalShow] = useHistoryState('', false);
+ const [definitionEditing, setDefinitionEditing] = useHistoryState('', undefined);
+ const [definitionToDelete, setDefinitionToDelete] = useState(undefined);
+ const { mutateAsync: deleteGuestDefinition } = useDeleteGuestDefinition();
+ const toast = useToast();
+
+ const { isLoading, isError, error, data: allDefinitions } = useAllGuestDefinitions();
+
+ const handleDelete = async (id: string) => {
+ setDefinitionToDelete(undefined);
+ try {
+ await deleteGuestDefinition(id);
+ toast.success('Guest definition deleted successfully');
+ } catch (e: unknown) {
+ if (e instanceof Error) {
+ toast.error(e.message, 3000);
+ }
+ }
+ };
+
+ if (isError) return ;
+ if (!allDefinitions || isLoading) return ;
+
+ const definitions = allDefinitions.filter((d) => d.type === type);
+
+ const rows = definitions.map((definition: GuestDefinition, index: number) => (
+
+
+ {definition.term}
+
+
+ {definition.description}
+
+
+
+
+
+
+ ));
+
+ return (
+
+ {createModalShow && (
+ setCreateModalShow(false)} type={type} />
+ )}
+ {definitionEditing && (
+ setDefinitionEditing(undefined)}
+ definition={definitionEditing}
+ />
+ )}
+
+
+
+
+ Term
+
+
+ Description
+
+
+
+ {rows}
+
+
+ setCreateModalShow(true)}>
+ Add Guest Definition
+
+
+ {definitionToDelete && (
+ setDefinitionToDelete(undefined)}
+ formId="delete-guest-definition-form"
+ dataType="Guest Definition"
+ onFormSubmit={() => {
+ handleDelete(definitionToDelete.definitionId);
+ }}
+ />
+ )}
+
+ );
+};
+
+export default GuestDefinitionsTable;
diff --git a/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestViewConfig.tsx b/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestViewConfig.tsx
index bc7a24613f..de745504c6 100644
--- a/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestViewConfig.tsx
+++ b/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestViewConfig.tsx
@@ -20,6 +20,8 @@ import { useToast } from '../../../hooks/toasts.hooks';
import { MAX_FILE_SIZE } from 'shared';
import UsefulLinksTable from '../OnboardingConfig/UsefulLinks/UsefulLinksTable';
import LinkTypeTable from '../ProjectsConfig/LinkTypes/LinkTypeTable';
+import GuestDefinitionsTable from './GuestDefinitionsTable';
+import { GuestDefinitionType } from 'shared';
const platformDescriptionSchema = yup.object().shape({
platformDescription: yup.string().required()
@@ -178,6 +180,18 @@ const GuestViewConfig: React.FC = () => {
+
+
+ Project Management Definitions
+
+
+
+
+
+ Info Page Definitions
+
+
+
);
};
diff --git a/src/frontend/src/utils/form.ts b/src/frontend/src/utils/form.ts
index 7f4bfe3daa..2d34d337db 100644
--- a/src/frontend/src/utils/form.ts
+++ b/src/frontend/src/utils/form.ts
@@ -86,5 +86,7 @@ export enum FormStorageKey {
CREATE_MACHINERY = 'CREATE_MACHINERY',
EDIT_MACHINERY = 'EDIT_MACHINERY',
CREATE_EVENT_TYPE = 'CREATE_EVENT_TYPE',
- EDIT_EVENT_TYPE = 'EDIT_EVENT_TYPE'
+ EDIT_EVENT_TYPE = 'EDIT_EVENT_TYPE',
+ EDIT_GUEST_DEFINITION = 'EDIT_GUEST_DEFINITION',
+ CREATE_GUEST_DEFINITION = 'CREATE_GUEST_DEFINITION'
}