From 009f768820024fa09e99e89ef21e0a195d36c3e5 Mon Sep 17 00:00:00 2001
From: NatSquared <nat.k.weiland@gmail.com>
Date: Thu, 15 Aug 2024 17:06:54 -0700
Subject: [PATCH 1/8] DESENG-667: Add engagement config summary section to
 admin view

---
 CHANGELOG.MD                                  |   9 +
 .../common/Communication/StatusIcon.tsx       |  10 +-
 .../common/Indicators/StatusChip.tsx          |  10 +-
 .../common/Layout/SystemMessage.tsx           |   4 +-
 .../DateRangePickerWithCalculation.tsx        |   8 +-
 .../EngagementCreateAction.tsx}               |   2 +-
 .../admin/config/EngagementUpdateAction.tsx   |  65 +++++
 .../EngagementVisibilityControl.tsx           |  10 +-
 .../FeedbackMethodSelector.tsx                |   4 +-
 .../LanguageLoader.tsx}                       |   0
 .../{create => config}/LanguageManager.tsx    |  84 +++---
 .../admin/{create => config}/MultiSelect.tsx  |   0
 .../admin/{create => config}/UserManager.tsx  |   3 +-
 .../admin/config/wizard/ConfigWizard.tsx      | 147 +++++++++++
 .../wizard/CreationWizard.tsx}                |  16 +-
 .../{create/form => config/wizard}/index.tsx  |  25 +-
 .../engagement/admin/view/ConfigSummary.tsx   | 243 ++++++++++++++++++
 .../engagement/admin/view/StatusChip.tsx      |  36 +++
 .../engagement/admin/view/index.tsx           |  87 ++++++-
 .../public/view/EngagementLoader.tsx          |  45 +++-
 .../engagement/public/view/index.tsx          |   1 +
 met-web/src/routes/AuthenticatedRoutes.tsx    |  47 ++--
 .../src/services/engagementService/types.ts   |   1 +
 met-web/src/styles/Theme.ts                   |  32 +--
 24 files changed, 755 insertions(+), 134 deletions(-)
 rename met-web/src/components/engagement/admin/{create => config}/DateRangePickerWithCalculation.tsx (98%)
 rename met-web/src/components/engagement/admin/{create/engagementCreateAction.tsx => config/EngagementCreateAction.tsx} (95%)
 create mode 100644 met-web/src/components/engagement/admin/config/EngagementUpdateAction.tsx
 rename met-web/src/components/engagement/admin/{create => config}/EngagementVisibilityControl.tsx (97%)
 rename met-web/src/components/engagement/admin/{create => config}/FeedbackMethodSelector.tsx (92%)
 rename met-web/src/components/engagement/admin/{create/languageLoader.tsx => config/LanguageLoader.tsx} (100%)
 rename met-web/src/components/engagement/admin/{create => config}/LanguageManager.tsx (70%)
 rename met-web/src/components/engagement/admin/{create => config}/MultiSelect.tsx (100%)
 rename met-web/src/components/engagement/admin/{create => config}/UserManager.tsx (97%)
 create mode 100644 met-web/src/components/engagement/admin/config/wizard/ConfigWizard.tsx
 rename met-web/src/components/engagement/admin/{create/index.tsx => config/wizard/CreationWizard.tsx} (69%)
 rename met-web/src/components/engagement/admin/{create/form => config/wizard}/index.tsx (85%)
 create mode 100644 met-web/src/components/engagement/admin/view/ConfigSummary.tsx
 create mode 100644 met-web/src/components/engagement/admin/view/StatusChip.tsx

diff --git a/CHANGELOG.MD b/CHANGELOG.MD
index 6f79d05a4..79048db4f 100644
--- a/CHANGELOG.MD
+++ b/CHANGELOG.MD
@@ -1,3 +1,12 @@
+## August 15, 2024
+
+- **Feature** Add engagement configuration summary [🎟️ DESENG-667](https://citz-gdx.atlassian.net/browse/DESENG-667)
+  - Added a tabbed layout to the new engagement view page
+  - Added a new "Configuration" tab to display the engagement's configuration details
+    - The other tabs are blank for now, but will be filled in future tickets
+  - The configuration tab allows navigating back to the engagement configuration page
+  - Improvements to how engagements and related resources are fetched and saved
+
 ## August 8, 2024
 
 - **Feature** New engagement details page [🎟️ DESENG-666](https://citz-gdx.atlassian.net/browse/DESENG-666)
diff --git a/met-web/src/components/common/Communication/StatusIcon.tsx b/met-web/src/components/common/Communication/StatusIcon.tsx
index 1a3c5f343..68fe3d522 100644
--- a/met-web/src/components/common/Communication/StatusIcon.tsx
+++ b/met-web/src/components/common/Communication/StatusIcon.tsx
@@ -26,24 +26,24 @@ type IconWeight = 'solid' | 'regular' | 'light';
 export const StatusIcon = ({
     status,
     color,
-    weight = 'solid',
+    weight = 'regular',
     ...props
 }: {
-    status: 'success' | 'warning' | 'error' | 'info';
+    status: 'success' | 'warning' | 'danger' | 'info';
     color?: string;
     weight?: IconWeight;
 } & Partial<FontAwesomeIconProps>) => {
     let iconMap = {
         success: faCheckCircle,
         warning: faExclamationTriangle,
-        error: faExclamationCircle,
+        danger: faExclamationCircle,
         info: faInfoCircle,
     };
     if (weight === 'regular') {
         iconMap = {
             success: faCheckCircleRegular,
             warning: faExclamationTriangleRegular,
-            error: faExclamationCircleRegular,
+            danger: faExclamationCircleRegular,
             info: faInfoCircleRegular,
         };
     }
@@ -51,7 +51,7 @@ export const StatusIcon = ({
         iconMap = {
             success: faCheckCircleLight,
             warning: faExclamationTriangleLight,
-            error: faExclamationCircleLight,
+            danger: faExclamationCircleLight,
             info: faInfoCircleLight,
         };
     }
diff --git a/met-web/src/components/common/Indicators/StatusChip.tsx b/met-web/src/components/common/Indicators/StatusChip.tsx
index 3ae08476c..3ae31ff15 100644
--- a/met-web/src/components/common/Indicators/StatusChip.tsx
+++ b/met-web/src/components/common/Indicators/StatusChip.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { Chip as MuiChip, Skeleton, useTheme } from '@mui/material';
+import { ChipProps as MuiChipProps, Chip as MuiChip, Skeleton, useTheme } from '@mui/material';
 import { colors } from '..';
 import { SubmissionStatus } from 'constants/engagementStatus';
 
@@ -23,12 +23,17 @@ export const getStatusFromStatusId = (statusId: SubmissionStatus): StatusText =>
     }
 };
 
-export const EngagementStatusChip: React.FC<ChipProps> = ({ label: customLabel, statusId: status }) => {
+export const EngagementStatusChip: React.FC<ChipProps & Partial<MuiChipProps>> = ({
+    label: customLabel,
+    statusId: status,
+    ...props
+}) => {
     const statusText = getStatusFromStatusId(status);
     const theme = useTheme();
     const invert = theme.palette.mode === 'dark';
     return (
         <MuiChip
+            {...props}
             label={customLabel || statusText}
             className={`status-chip status-chip-${statusText.toLowerCase()} ${invert ? 'status-chip-invert' : ''}`}
             sx={{
@@ -74,6 +79,7 @@ export const EngagementStatusChip: React.FC<ChipProps> = ({ label: customLabel,
                     borderColor: colors.surface.gray[100],
                     color: colors.surface.gray[40],
                 },
+                ...props.sx,
             }}
         />
     );
diff --git a/met-web/src/components/common/Layout/SystemMessage.tsx b/met-web/src/components/common/Layout/SystemMessage.tsx
index 85bea6435..6b33cf220 100644
--- a/met-web/src/components/common/Layout/SystemMessage.tsx
+++ b/met-web/src/components/common/Layout/SystemMessage.tsx
@@ -14,7 +14,7 @@ export const SystemMessage = ({
     children,
     ...props
 }: {
-    status: 'success' | 'warning' | 'error' | 'info';
+    status: 'success' | 'warning' | 'danger' | 'info';
     onDismiss?: () => void;
     color?: string;
     coloredBackground?: boolean;
@@ -30,7 +30,7 @@ export const SystemMessage = ({
                 maxWidth: { xs: '100%', md: '700px' },
                 borderRadius: '8px',
                 backgroundColor: coloredBackground ? colors.notification[status].tint : 'transparent',
-                color: 'type.primary',
+                color: 'text.primary',
                 padding: '0.8rem 1rem',
                 paddingLeft: { xs: '0.5rem', md: '1rem' },
                 border: `1px solid ${colors.notification[status].shade}`,
diff --git a/met-web/src/components/engagement/admin/create/DateRangePickerWithCalculation.tsx b/met-web/src/components/engagement/admin/config/DateRangePickerWithCalculation.tsx
similarity index 98%
rename from met-web/src/components/engagement/admin/create/DateRangePickerWithCalculation.tsx
rename to met-web/src/components/engagement/admin/config/DateRangePickerWithCalculation.tsx
index 83e617355..104caea8f 100644
--- a/met-web/src/components/engagement/admin/create/DateRangePickerWithCalculation.tsx
+++ b/met-web/src/components/engagement/admin/config/DateRangePickerWithCalculation.tsx
@@ -44,12 +44,16 @@ export const DateRangePickerWithCalculation = () => {
             if (name === 'end_date') {
                 trigger('end_date');
             }
-            if (!value?.end_date) return;
-            setNumberOfDays(value.end_date.clone().add(1, 'second').diff(value.start_date, 'days'));
         });
         return () => subscription.unsubscribe();
     }, [watch]);
 
+    useEffect(() => {
+        if (startDate && endDate) {
+            setNumberOfDays(endDate.clone().add(1, 'second').diff(startDate, 'days'));
+        }
+    }, [startDate, endDate]);
+
     const getDayStyle = (props: PickersDayProps<Dayjs | null>) => {
         const standardStyle = {
             margin: 0,
diff --git a/met-web/src/components/engagement/admin/create/engagementCreateAction.tsx b/met-web/src/components/engagement/admin/config/EngagementCreateAction.tsx
similarity index 95%
rename from met-web/src/components/engagement/admin/create/engagementCreateAction.tsx
rename to met-web/src/components/engagement/admin/config/EngagementCreateAction.tsx
index 8a122c7c8..bfc8a3f05 100644
--- a/met-web/src/components/engagement/admin/create/engagementCreateAction.tsx
+++ b/met-web/src/components/engagement/admin/config/EngagementCreateAction.tsx
@@ -26,7 +26,7 @@ export const engagementCreateAction: ActionFunction = async ({ request }) => {
     formData.getAll('users').forEach((user_id) => {
         addTeamMemberToEngagement({ user_id: user_id.toString(), engagement_id: engagement.id });
     });
-    return redirect(`/engagements/${engagement.id}/form`);
+    return redirect(`/engagements/${engagement.id}/view`);
 };
 
 export default engagementCreateAction;
diff --git a/met-web/src/components/engagement/admin/config/EngagementUpdateAction.tsx b/met-web/src/components/engagement/admin/config/EngagementUpdateAction.tsx
new file mode 100644
index 000000000..a560ad13b
--- /dev/null
+++ b/met-web/src/components/engagement/admin/config/EngagementUpdateAction.tsx
@@ -0,0 +1,65 @@
+import { ENGAGEMENT_MEMBERSHIP_STATUS } from 'models/engagementTeamMember';
+import { ActionFunction, redirect } from 'react-router-dom';
+import { patchEngagement } from 'services/engagementService';
+import { patchEngagementSlug } from 'services/engagementSlugService';
+import {
+    addTeamMemberToEngagement,
+    revokeMembership,
+    reinstateMembership,
+    getTeamMembers,
+} from 'services/membershipService';
+
+export const engagementUpdateAction: ActionFunction = async ({ request, params }) => {
+    const formData = (await request.formData()) as FormData;
+    const engagementId = Number(params.engagementId);
+    await patchEngagement({
+        id: engagementId,
+        name: formData.get('name') as string,
+        start_date: formData.get('start_date') as string,
+        end_date: formData.get('end_date') as string,
+        is_internal: formData.get('is_internal') === 'true',
+    });
+    try {
+        await patchEngagementSlug({
+            engagement_id: engagementId,
+            slug: formData.get('slug') as string,
+        });
+    } catch (e) {
+        console.error('Error updating engagement slug', e);
+    }
+
+    const currentTeamMembers = await getTeamMembers({ engagement_id: engagementId });
+    const users = formData.getAll('users') as string[];
+    const usersSet = new Set(users);
+
+    try {
+        // Process deactivated users for reinstatement (and active users for revocation)
+        // Caution - headaches ahead! There is a big difference between user_id and user.external_id
+        for (const member of currentTeamMembers) {
+            const isUserInForm = usersSet.has(String(member.user.external_id));
+            if (member.status !== ENGAGEMENT_MEMBERSHIP_STATUS.Active) {
+                if (isUserInForm) {
+                    // If the user was previously deactivated, reinstate them
+                    reinstateMembership(engagementId, member.user_id);
+                }
+            } else {
+                if (!isUserInForm) {
+                    // If the user was previously active but is not in the form, revoke their membership
+                    revokeMembership(engagementId, member.user_id);
+                }
+            }
+            // Remove all known users from the set so we can add new members in the next step
+            usersSet.delete(String(member.user.external_id));
+        }
+        // Add new members that weren't in the current team members list
+        for (const user of usersSet) {
+            addTeamMemberToEngagement({ user_id: user, engagement_id: engagementId });
+        }
+    } catch (e) {
+        console.error('Error updating team members', e);
+    }
+
+    return redirect(`/engagements/${engagementId}/view`);
+};
+
+export default engagementUpdateAction;
diff --git a/met-web/src/components/engagement/admin/create/EngagementVisibilityControl.tsx b/met-web/src/components/engagement/admin/config/EngagementVisibilityControl.tsx
similarity index 97%
rename from met-web/src/components/engagement/admin/create/EngagementVisibilityControl.tsx
rename to met-web/src/components/engagement/admin/config/EngagementVisibilityControl.tsx
index 2ab6ddfc4..a5850c250 100644
--- a/met-web/src/components/engagement/admin/create/EngagementVisibilityControl.tsx
+++ b/met-web/src/components/engagement/admin/config/EngagementVisibilityControl.tsx
@@ -21,11 +21,11 @@ const EngagementVisibilityControl = () => {
     const isInternal = watch('is_internal');
     const formSlug = watch('slug');
     const isConfirmed = watch('_visibilityConfirmed');
-    const setIsConfirmed = (value: boolean) => setValue('_visibilityConfirmed', value);
+    const setIsConfirmed = (value: boolean) => setValue('_visibilityConfirmed', value, { shouldDirty: true });
 
     const [isEditing, setIsEditing] = React.useState(false);
     const [currentSlug, setCurrentSlug] = React.useState(formSlug);
-    const [hasBeenEdited, setHasBeenEdited] = React.useState(false);
+    const [hasBeenEdited, setHasBeenEdited] = React.useState(isConfirmed);
 
     useEffect(() => {
         const subscription = watch((value, { name, type }) => {
@@ -41,7 +41,7 @@ const EngagementVisibilityControl = () => {
                     })
                     .join('')
                     .toLowerCase();
-                setValue('slug', newSlug);
+                setValue('slug', newSlug, { shouldDirty: true });
                 setCurrentSlug(newSlug);
             }
         });
@@ -125,7 +125,7 @@ const EngagementVisibilityControl = () => {
                                             onClick={() => {
                                                 setIsConfirmed(true);
                                                 setHasBeenEdited(true);
-                                                setValue('slug', currentSlug);
+                                                setValue('slug', currentSlug, { shouldDirty: true });
                                             }}
                                         >
                                             Confirm
@@ -178,7 +178,7 @@ const EngagementVisibilityControl = () => {
                                 disabled={!currentSlug}
                                 variant="primary"
                                 onClick={() => {
-                                    setValue('slug', currentSlug);
+                                    setValue('slug', currentSlug, { shouldDirty: true });
                                     setHasBeenEdited(true);
                                     setIsConfirmed(true);
                                     setIsEditing(false);
diff --git a/met-web/src/components/engagement/admin/create/FeedbackMethodSelector.tsx b/met-web/src/components/engagement/admin/config/FeedbackMethodSelector.tsx
similarity index 92%
rename from met-web/src/components/engagement/admin/create/FeedbackMethodSelector.tsx
rename to met-web/src/components/engagement/admin/config/FeedbackMethodSelector.tsx
index dff2c1415..9d8c3e9b4 100644
--- a/met-web/src/components/engagement/admin/create/FeedbackMethodSelector.tsx
+++ b/met-web/src/components/engagement/admin/config/FeedbackMethodSelector.tsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import { Grid, Checkbox, FormControlLabel } from '@mui/material';
 import { SystemMessage } from 'components/common/Layout/SystemMessage';
-import { EngagementConfigurationData } from './form';
+import { EngagementConfigurationData } from './wizard';
 import { useFormContext } from 'react-hook-form';
 
 export const FeedbackMethodSelector = () => {
@@ -26,6 +26,7 @@ export const FeedbackMethodSelector = () => {
                             checked
                                 ? [...watch('feedback_methods'), 'survey']
                                 : watch('feedback_methods').filter((m) => m !== 'survey'),
+                            { shouldDirty: true },
                         );
                     }}
                 />
@@ -40,6 +41,7 @@ export const FeedbackMethodSelector = () => {
                             checked
                                 ? [...watch('feedback_methods'), '3rd_party']
                                 : watch('feedback_methods').filter((m) => m !== '3rd_party'),
+                            { shouldDirty: true },
                         );
                     }}
                 />
diff --git a/met-web/src/components/engagement/admin/create/languageLoader.tsx b/met-web/src/components/engagement/admin/config/LanguageLoader.tsx
similarity index 100%
rename from met-web/src/components/engagement/admin/create/languageLoader.tsx
rename to met-web/src/components/engagement/admin/config/LanguageLoader.tsx
diff --git a/met-web/src/components/engagement/admin/create/LanguageManager.tsx b/met-web/src/components/engagement/admin/config/LanguageManager.tsx
similarity index 70%
rename from met-web/src/components/engagement/admin/create/LanguageManager.tsx
rename to met-web/src/components/engagement/admin/config/LanguageManager.tsx
index 6bcc04dde..d334800e3 100644
--- a/met-web/src/components/engagement/admin/create/LanguageManager.tsx
+++ b/met-web/src/components/engagement/admin/config/LanguageManager.tsx
@@ -1,7 +1,7 @@
 import React, { useEffect } from 'react';
 import { Box, FormControlLabel, Grid, Radio, RadioGroup, TextField } from '@mui/material';
 import { textInputStyles } from 'components/common/Input/TextInput';
-import { useAsyncValue } from 'react-router-dom';
+import { useAsyncValue, useFetcher } from 'react-router-dom';
 import { BodyText } from 'components/common/Typography';
 import { When } from 'react-if';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -13,50 +13,58 @@ import MultiSelect from './MultiSelect';
 import { SystemMessage } from 'components/common/Layout/SystemMessage';
 
 export const LanguageManager = () => {
+    const SINGLE_LANGUAGE = [{ code: 'en', name: 'English' }] as Language[];
+    const REQUIRED_LANGUAGES = [
+        { code: 'en', name: 'English' },
+        { code: 'fr', name: 'French' },
+    ] as Language[];
+    const requiredLanguageCodes = REQUIRED_LANGUAGES.map((l) => l.code);
+
     const engagementForm = useFormContext();
     const { setValue, watch } = engagementForm;
     const selectedLanguages = watch('languages') as Language[];
-    const [isSingleLanguage, setIsSingleLanguage] = React.useState<boolean | null>(null);
-    const requiredLanguages = isSingleLanguage !== false ? ['en'] : ['en', 'fr'];
-    const availableLanguages = useAsyncValue() as Language[];
-    const requiredLanguagesAvailable = requiredLanguages.filter((l) =>
-        availableLanguages.map((l) => l.code).includes(l),
-    );
+    // const [isSingleLanguage, setIsSingleLanguage] = React.useState<boolean | null>(null);
+    const fetcher = useFetcher();
+    const fetcherData = fetcher.data as { languages: Language[] } | undefined;
+    const { languages: availableLanguages } = fetcherData ?? { languages: [] };
 
     const [searchTerm, setSearchTerm] = React.useState('');
 
-    useEffect(() => {
-        // Don't do anything if language multiplicity has not been indicated
-        if (isSingleLanguage === null) return;
+    const determineSingleLanguage = (languages: Language[]) => {
+        if (languages.length === 0) return null;
+        if (languages.length === 1) return true;
+        return false;
+    };
+    const isSingleLanguage = determineSingleLanguage(selectedLanguages);
 
-        // If it's english only, remove any other languages
-        if (isSingleLanguage) {
-            setValue('languages', [{ code: 'en', name: 'English' }]);
-            return;
-        }
-        // If the required languages are not included, add them
-        if (requiredLanguagesAvailable.length) {
-            const languagesToAdd = availableLanguages.filter(
-                (l) =>
-                    requiredLanguagesAvailable.includes(l.code) &&
-                    !watch('languages')
-                        .map((l: Language) => l.code)
-                        .includes(l.code),
-            );
-            setValue('languages', [...watch('languages'), ...languagesToAdd]);
-        }
-    }, [watch, isSingleLanguage]);
+    useEffect(() => {
+        fetcher.load('/languages/');
+    }, []);
 
+    if (!fetcherData) return null;
     return (
         <Box width="100%">
             <RadioGroup
-                onChange={(e) => setIsSingleLanguage(e.target.value === 'true')}
+                onChange={(e) => {
+                    if (e.target.value === 'single') {
+                        setValue('languages', SINGLE_LANGUAGE, { shouldDirty: true });
+                    }
+                    if (e.target.value === 'multi') {
+                        const optionalLanguages = selectedLanguages.filter(
+                            (l) => !requiredLanguageCodes.includes(l.code),
+                        );
+                        setValue('languages', [...REQUIRED_LANGUAGES, ...optionalLanguages], {
+                            shouldDirty: true,
+                            shouldValidate: true,
+                        });
+                    }
+                }}
                 aria-label="Select Engagement's Language Type"
                 name="languageType"
-                value={isSingleLanguage}
+                value={isSingleLanguage && (isSingleLanguage ? 'single' : 'multi')}
             >
-                <FormControlLabel value={true} control={<Radio />} label="English Only" />
-                <FormControlLabel value={false} control={<Radio />} label="Multi-language" />
+                <FormControlLabel value={'single'} control={<Radio />} label="English Only" />
+                <FormControlLabel value={'multi'} control={<Radio />} label="Multi-language" />
             </RadioGroup>
             <When condition={isSingleLanguage === false}>
                 <SystemMessage status="warning">
@@ -68,11 +76,15 @@ export const LanguageManager = () => {
                         if (reason === 'removeOption' && language) {
                             setValue(
                                 'languages',
-                                selectedLanguages.filter((l) => l.code !== language.code),
+                                selectedLanguages.filter((l) => l.code !== language.code, {
+                                    shouldDirty: true,
+                                }),
                             );
                         }
                         if (reason === 'selectOption' && language) {
-                            setValue('languages', [...selectedLanguages, language]);
+                            setValue('languages', [...selectedLanguages, language], {
+                                shouldDirty: true,
+                            });
                         }
                     }}
                     options={availableLanguages ?? []}
@@ -98,9 +110,9 @@ export const LanguageManager = () => {
                         return (
                             <Grid container direction="row" spacing={1} alignItems="center">
                                 <Grid item>
-                                    <BodyText bold={requiredLanguagesAvailable.includes(option.code)}>
+                                    <BodyText bold={requiredLanguageCodes.includes(option.code)}>
                                         {`${option.name}`}
-                                        {requiredLanguagesAvailable.includes(option.code) && ' (Default)'}
+                                        {requiredLanguageCodes.includes(option.code) && ' (Default)'}
                                     </BodyText>
                                 </Grid>
                             </Grid>
@@ -142,7 +154,7 @@ export const LanguageManager = () => {
                     selectedLabel={{ singular: 'Language Added', plural: 'Languages Added' }}
                     searchPlaceholder="Select Language"
                     getOptionDisabled={(option) => selectedLanguages.filter((l) => l.code === option.code).length > 0}
-                    getOptionRequired={(option) => requiredLanguagesAvailable.includes(option.code)}
+                    getOptionRequired={(option) => requiredLanguageCodes.includes(option.code)}
                 />
             </When>
         </Box>
diff --git a/met-web/src/components/engagement/admin/create/MultiSelect.tsx b/met-web/src/components/engagement/admin/config/MultiSelect.tsx
similarity index 100%
rename from met-web/src/components/engagement/admin/create/MultiSelect.tsx
rename to met-web/src/components/engagement/admin/config/MultiSelect.tsx
diff --git a/met-web/src/components/engagement/admin/create/UserManager.tsx b/met-web/src/components/engagement/admin/config/UserManager.tsx
similarity index 97%
rename from met-web/src/components/engagement/admin/create/UserManager.tsx
rename to met-web/src/components/engagement/admin/config/UserManager.tsx
index 53d793a6a..e47b7ed4b 100644
--- a/met-web/src/components/engagement/admin/create/UserManager.tsx
+++ b/met-web/src/components/engagement/admin/config/UserManager.tsx
@@ -47,7 +47,7 @@ export const UserManager = () => {
 
     const handleAddUser = (user: User) => {
         if (!selectedUsers.filter((u) => u.id === user.id).length) {
-            setValue('users', [...selectedUsers, user]);
+            setValue('users', [...selectedUsers, user], { shouldDirty: true });
         }
     };
 
@@ -55,6 +55,7 @@ export const UserManager = () => {
         setValue(
             'users',
             selectedUsers.filter((u) => u !== user),
+            { shouldDirty: true },
         );
     };
 
diff --git a/met-web/src/components/engagement/admin/config/wizard/ConfigWizard.tsx b/met-web/src/components/engagement/admin/config/wizard/ConfigWizard.tsx
new file mode 100644
index 000000000..3929b4ab1
--- /dev/null
+++ b/met-web/src/components/engagement/admin/config/wizard/ConfigWizard.tsx
@@ -0,0 +1,147 @@
+import React, { Suspense } from 'react';
+import { ResponsiveContainer } from 'components/common/Layout';
+import {
+    useFetcher,
+    createSearchParams,
+    useRouteLoaderData,
+    Await,
+    useAsyncValue,
+    useNavigation,
+} from 'react-router-dom';
+import { FormProvider, useForm } from 'react-hook-form';
+import { AutoBreadcrumbs } from 'components/common/Navigation/Breadcrumb';
+import EngagementForm, { EngagementConfigurationData } from '.';
+import { EngagementLoaderData } from 'components/engagement/public/view';
+import { Engagement } from 'models/engagement';
+import { ENGAGEMENT_MEMBERSHIP_STATUS, EngagementTeamMember } from 'models/engagementTeamMember';
+import { BodyText, Header1, Header2 } from 'components/common/Typography';
+import dayjs from 'dayjs';
+import { Language } from 'models/language';
+import { CircularProgress, Grid, Modal, Skeleton } from '@mui/material';
+import { modalStyle } from 'components/common';
+
+const EngagementConfigurationWizard = () => {
+    const { engagement, teamMembers, slug } = useRouteLoaderData('single-engagement') as EngagementLoaderData;
+    return (
+        <ResponsiveContainer>
+            <AutoBreadcrumbs />
+            <Suspense
+                fallback={
+                    <Skeleton variant="text">
+                        <Header1 sx={{ mb: 0 }}>Example Engagement</Header1>
+                    </Skeleton>
+                }
+            >
+                <Await resolve={engagement}>
+                    {(resolvedEngagement) => <Header1 sx={{ mb: 0 }}>{resolvedEngagement.name}</Header1>}
+                </Await>
+            </Suspense>
+            <br />
+            <Suspense fallback={<Header2 decorated>Edit Configuration</Header2>}>
+                <Await resolve={Promise.all([engagement, teamMembers, slug])}>
+                    <ConfigForm />
+                </Await>
+            </Suspense>
+        </ResponsiveContainer>
+    );
+};
+
+const ConfigForm = () => {
+    const [engagement, teamMembers, slug] = useAsyncValue() as [Engagement, EngagementTeamMember[], string];
+    const fetcher = useFetcher();
+    const navigation = useNavigation();
+
+    const engagementConfigForm = useForm<EngagementConfigurationData>({
+        defaultValues: {
+            name: engagement.name,
+            feedback_methods: [],
+            start_date: dayjs(engagement.start_date),
+            end_date: dayjs(engagement.end_date),
+            _dateConfirmed: true,
+            languages: [{ code: 'en', name: 'English' }] as Language[],
+            is_internal: engagement.is_internal,
+            _visibilityConfirmed: true,
+            slug: slug,
+            users: teamMembers.filter((tm) => tm.status == ENGAGEMENT_MEMBERSHIP_STATUS.Active).map((tm) => tm.user),
+        },
+        mode: 'onSubmit',
+        reValidateMode: 'onChange',
+    });
+
+    const onSubmit = async (data: EngagementConfigurationData) => {
+        fetcher.submit(
+            createSearchParams({
+                name: data.name,
+                feedback_methods: data.feedback_methods,
+                start_date: data.start_date.format('YYYY-MM-DD'),
+                end_date: data.end_date.format('YYYY-MM-DD'),
+                languages: data.languages.map((l) => l.code),
+                is_internal: data.is_internal ? 'true' : 'false',
+                slug: data.slug,
+                users: data.users.map((u) => u.external_id),
+            }),
+            {
+                method: 'patch',
+                action: `/engagements/${engagement.id}/config/`,
+            },
+        );
+    };
+
+    const {
+        formState: { isSubmitting, isSubmitted },
+    } = engagementConfigForm;
+
+    return (
+        <FormProvider {...engagementConfigForm}>
+            <EngagementForm onSubmit={onSubmit} />
+            <Modal
+                open={
+                    isSubmitting || isSubmitted || fetcher.state === 'submitting' || navigation.state === 'submitting'
+                }
+            >
+                <Grid
+                    container
+                    direction="row"
+                    justifyContent="flex-start"
+                    alignItems="flex-start"
+                    sx={{ ...modalStyle, borderColor: 'notification.default.shade' }}
+                >
+                    <Grid item xs={1} sx={{ pt: 1.25, fontSize: '16px' }}>
+                        <CircularProgress
+                            variant="indeterminate"
+                            sx={{
+                                color: 'notification.default.shade',
+                                width: '24px',
+                                height: '24px',
+                                animationDuration: '550ms',
+                                '& .MuiCircularProgress-circle': {
+                                    strokeLinecap: 'round',
+                                },
+                            }}
+                        />
+                    </Grid>
+                    <Grid
+                        item
+                        xs={11}
+                        container
+                        direction="row"
+                        justifyContent="flex-start"
+                        alignItems="space-between"
+                        rowSpacing={1}
+                    >
+                        <Grid container direction="row" item xs={12}>
+                            <Grid xs={12}>
+                                <Header2 sx={{ mb: 0 }}>We're just looking over your configuration.</Header2>
+                            </Grid>
+                        </Grid>
+                        <Grid container direction="row" item xs={12}>
+                            <BodyText bold>This should only take a few seconds.</BodyText>
+                        </Grid>
+                    </Grid>
+                </Grid>
+            </Modal>
+        </FormProvider>
+    );
+};
+
+export default EngagementConfigurationWizard;
diff --git a/met-web/src/components/engagement/admin/create/index.tsx b/met-web/src/components/engagement/admin/config/wizard/CreationWizard.tsx
similarity index 69%
rename from met-web/src/components/engagement/admin/create/index.tsx
rename to met-web/src/components/engagement/admin/config/wizard/CreationWizard.tsx
index c84d9a2c2..92af7f1c5 100644
--- a/met-web/src/components/engagement/admin/create/index.tsx
+++ b/met-web/src/components/engagement/admin/config/wizard/CreationWizard.tsx
@@ -3,7 +3,10 @@ import { ResponsiveContainer } from 'components/common/Layout';
 import { useFetcher, createSearchParams } from 'react-router-dom';
 import { FormProvider, useForm } from 'react-hook-form';
 import { AutoBreadcrumbs } from 'components/common/Navigation/Breadcrumb';
-import EngagementForm, { EngagementConfigurationData } from './form';
+import EngagementForm, { EngagementConfigurationData } from '.';
+import { Header1, Header2 } from 'components/common/Typography';
+import { SystemMessage } from 'components/common/Layout/SystemMessage';
+import { Link } from 'components/common/Navigation';
 
 const EngagementCreationWizard = () => {
     const fetcher = useFetcher();
@@ -47,6 +50,17 @@ const EngagementCreationWizard = () => {
     return (
         <ResponsiveContainer>
             <AutoBreadcrumbs />
+            <Header1 sx={{ mb: 0 }}>New Engagement</Header1>
+            <Header2 weight="thin">Create a new engagement in six easy configuration steps.</Header2>
+            <SystemMessage status="info">
+                You will be able to modify the configuration of your engagement later in the case the parameters of your
+                engagement change. If you prefer, you can use{' '}
+                <Link size="small" to="../form">
+                    the old form
+                </Link>
+                .
+            </SystemMessage>
+            <br />
             <FormProvider {...engagementCreationForm}>
                 <EngagementForm isNewEngagement onSubmit={onSubmit} />
             </FormProvider>
diff --git a/met-web/src/components/engagement/admin/create/form/index.tsx b/met-web/src/components/engagement/admin/config/wizard/index.tsx
similarity index 85%
rename from met-web/src/components/engagement/admin/create/form/index.tsx
rename to met-web/src/components/engagement/admin/config/wizard/index.tsx
index 9c4f4fdd6..e9ccefd4b 100644
--- a/met-web/src/components/engagement/admin/create/form/index.tsx
+++ b/met-web/src/components/engagement/admin/config/wizard/index.tsx
@@ -13,8 +13,6 @@ import { LanguageManager } from '../LanguageManager';
 import { UserManager } from '../UserManager';
 import { User } from 'models/user';
 import { Language } from 'models/language';
-import { SystemMessage } from 'components/common/Layout/SystemMessage';
-import { Link } from 'components/common/Navigation';
 import { FormStep } from 'components/common/Layout/FormStep';
 
 export interface EngagementConfigurationData {
@@ -43,8 +41,6 @@ const EngagementForm = ({
     onSubmit: (data: EngagementConfigurationData) => void;
     isNewEngagement?: boolean;
 }) => {
-    const { languages } = useLoaderData() as { languages: Language[] };
-
     const engagementForm = useFormContext<EngagementConfigurationData>();
 
     const {
@@ -59,18 +55,7 @@ const EngagementForm = ({
     return (
         <Form onSubmit={handleSubmit(onSubmit)}>
             <Box sx={{ maxWidth: '788px' }}>
-                <Header1 sx={{ mb: 0 }}>New Engagement</Header1>
-                <Header2 weight="thin">Create a new engagement in six easy configuration steps.</Header2>
-                <SystemMessage status="info">
-                    You will be able to modify the configuration of your engagement later in the case the parameters of
-                    your engagement change. If you prefer, you can use{' '}
-                    <Link size="small" to="../form">
-                        the old form
-                    </Link>
-                    .
-                </SystemMessage>
-                <br />
-                <Header2 decorated>Configure Engagement</Header2>
+                <Header2 decorated>{isNewEngagement ? 'Configure Engagement' : 'Edit Configuration'}</Header2>
                 <br />
                 <Controller
                     control={control}
@@ -128,11 +113,7 @@ const EngagementForm = ({
                     details="All engagements must be offered in English, but you may also add content in additional languages. If you select multi-language, you must include French."
                     isGroup
                 >
-                    <Suspense fallback={<Skeleton variant="rectangular" sx={{ width: '100%', height: '288px' }} />}>
-                        <Await resolve={languages}>
-                            <LanguageManager />
-                        </Await>
-                    </Suspense>
+                    <LanguageManager />
                 </FormStep>
                 <FormStep
                     step={5}
@@ -171,7 +152,7 @@ const EngagementForm = ({
                 >
                     {isNewEngagement ? 'Create Engagement' : 'Save Changes'}
                 </Button>
-                <Button href="/engagements">Cancel</Button>
+                <Button href={isNewEngagement ? '/engagements' : '../view'}>Cancel</Button>
             </Box>
             <UnsavedWorkConfirmation blockNavigationWhen={isDirty && !isSubmitting} />
         </Form>
diff --git a/met-web/src/components/engagement/admin/view/ConfigSummary.tsx b/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
new file mode 100644
index 000000000..46bee0a24
--- /dev/null
+++ b/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
@@ -0,0 +1,243 @@
+import React, { Suspense, useEffect } from 'react';
+import { Avatar, Box, Grid, IconButton, Skeleton, Tooltip } from '@mui/material';
+import { BodyText, Header2 } from '../../../common/Typography';
+import { OutlineBox } from 'components/common/Layout';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faCopy } from '@fortawesome/pro-light-svg-icons';
+import { globalFocusVisible } from 'components/common';
+import { getBaseUrl } from 'helper';
+import { Await, useAsyncValue, useRouteLoaderData } from 'react-router-dom';
+import { Engagement } from 'models/engagement';
+import { EngagementStatusChip } from 'components/common/Indicators';
+import { SubmissionStatus } from 'constants/engagementStatus';
+import dayjs from 'dayjs';
+import { EngagementLoaderData } from 'components/engagement/public/view';
+import { ENGAGEMENT_MEMBERSHIP_STATUS, EngagementTeamMember } from 'models/engagementTeamMember';
+import { Button } from 'components/common/Input';
+import { faPen } from '@fortawesome/pro-regular-svg-icons';
+import { SystemMessage } from 'components/common/Layout/SystemMessage';
+
+export const ConfigSummary = () => {
+    const siteUrl = getBaseUrl();
+    const [engagement, teamMembers, slug] = useAsyncValue() as [Engagement, EngagementTeamMember[], string];
+    const [tooltipOpen, setTooltipOpen] = React.useState(false);
+
+    useEffect(() => {
+        if (tooltipOpen) {
+            const timer = setTimeout(() => {
+                setTooltipOpen(false);
+            }, 2000);
+            return () => {
+                clearTimeout(timer);
+            };
+        }
+    }, [tooltipOpen]);
+
+    return (
+        <Box>
+            <Header2 decorated>Configuration</Header2>
+            <Grid container spacing={2} width="624px" maxWidth="100%" direction="column">
+                <Grid item>
+                    <OutlineBox>
+                        <Grid container spacing={1} direction="column">
+                            <Grid item>
+                                <BodyText bold color="primary.main">
+                                    Engagement URL
+                                </BodyText>
+                            </Grid>
+                            <Grid item>
+                                <Tooltip arrow open={tooltipOpen} title="Copied!" placement="top">
+                                    <IconButton
+                                        size="small"
+                                        sx={{
+                                            backgroundColor: 'primary.light',
+                                            color: 'white',
+                                            width: '32px',
+                                            height: '32px',
+                                            '&:hover': {
+                                                backgroundColor: 'primary.main',
+                                            },
+                                            ...globalFocusVisible,
+                                            display: 'inline-block',
+                                            marginRight: '0.5rem',
+                                        }}
+                                        onClick={() => {
+                                            navigator.clipboard.writeText(`${siteUrl}/${slug}`);
+                                            setTooltipOpen(true);
+                                        }}
+                                    >
+                                        <FontAwesomeIcon
+                                            fontSize={16}
+                                            icon={faCopy}
+                                            style={{ position: 'relative', bottom: '4px' }}
+                                        />
+                                    </IconButton>
+                                </Tooltip>
+                                <BodyText sx={{ display: 'inline' }}>
+                                    <span style={{ fontWeight: 'bold' }}>{siteUrl}/</span>
+                                    {slug}
+                                </BodyText>
+                            </Grid>
+                        </Grid>
+                    </OutlineBox>
+                </Grid>
+                <Grid item>
+                    <OutlineBox>
+                        <Grid container direction="row" spacing={1}>
+                            <Grid item xs={12}>
+                                <BodyText bold color="primary.main">
+                                    Engagement Feedback Dates
+                                </BodyText>
+                            </Grid>
+                            <Grid item xs="auto" container direction="column" spacing={2}>
+                                <Grid item container spacing={1}>
+                                    <Grid item width={{ xs: '100%', sm: '82px' }}>
+                                        <EngagementStatusChip
+                                            statusId={SubmissionStatus.Open}
+                                            sx={{ '&>span.MuiChip-label': { padding: '4px 12px' } }}
+                                        />
+                                    </Grid>
+                                    <Grid item>
+                                        <BodyText bold sx={{ display: 'inline' }}>
+                                            {dayjs(engagement.start_date).format('MMMM D, YYYY')}
+                                        </BodyText>{' '}
+                                        <BodyText sx={{ display: { xs: 'block', sm: 'inline' } }}>(12:01 am)</BodyText>
+                                    </Grid>
+                                </Grid>
+                                <Grid item container spacing={1}>
+                                    <Grid item width={{ xs: '100%', sm: '82px' }}>
+                                        <EngagementStatusChip
+                                            statusId={SubmissionStatus.Closed}
+                                            sx={{ '&>span.MuiChip-label': { padding: '4px 12px' } }}
+                                        />
+                                    </Grid>
+                                    <Grid item>
+                                        <BodyText bold sx={{ display: 'inline' }}>
+                                            {dayjs(engagement.end_date).format('MMMM D, YYYY')}
+                                        </BodyText>{' '}
+                                        <BodyText sx={{ display: { xs: 'block', sm: 'inline' } }}>(11:59 pm)</BodyText>
+                                    </Grid>
+                                </Grid>
+                            </Grid>
+                            <Grid
+                                item
+                                xs="auto"
+                                sx={{
+                                    width: '100%',
+                                    mb: 1,
+                                    mt: { xs: 1, sm: 0 },
+                                    maxWidth: { xs: '100%', sm: 'fit-content' },
+                                }}
+                            >
+                                <BodyText bold size="large" sx={{ color: 'primary.light', lineHeight: 0.8 }}>
+                                    <span style={{ fontSize: '72px' }}>
+                                        {dayjs(engagement.end_date)
+                                            .clone()
+                                            .add(1, 'second')
+                                            .diff(dayjs(engagement.start_date), 'days')}
+                                    </span>
+                                    <span style={{ position: 'relative', bottom: '16px', fontSize: '24px' }}>
+                                        {' '}
+                                        days
+                                    </span>
+                                </BodyText>
+                            </Grid>
+                        </Grid>
+                    </OutlineBox>
+                </Grid>
+                <Grid item>
+                    <OutlineBox>
+                        <Grid container direction="column" spacing={1}>
+                            <Grid item>
+                                <BodyText bold color="primary.main">
+                                    Language(s) Included (1)
+                                </BodyText>
+                            </Grid>
+                            <Grid item>
+                                <BodyText bold>English (Default)</BodyText>
+                            </Grid>
+                        </Grid>
+                    </OutlineBox>
+                </Grid>
+                <Grid item>
+                    <OutlineBox>
+                        <Grid container direction="column" spacing={1}>
+                            <Grid item>
+                                <BodyText bold color="primary.main">
+                                    Team Member(s) Added
+                                </BodyText>
+                            </Grid>
+                            <Suspense fallback={<TeamMemberListSkeleton />}>
+                                <Await resolve={teamMembers}>
+                                    <TeamMemberList />
+                                </Await>
+                            </Suspense>
+                        </Grid>
+                    </OutlineBox>
+                </Grid>
+                <Grid item sx={{ pt: '40px' }}>
+                    <Button href="../config" variant="secondary" icon={<FontAwesomeIcon icon={faPen} />}>
+                        Edit Configuration
+                    </Button>
+                </Grid>
+            </Grid>
+        </Box>
+    );
+};
+
+const TeamMemberList = () => {
+    const teamMembers = (useAsyncValue() as EngagementTeamMember[]).filter(
+        (teamMember) => teamMember.status === ENGAGEMENT_MEMBERSHIP_STATUS.Active,
+    );
+    if (!teamMembers.length) {
+        return (
+            <Grid item>
+                <BodyText>No team members added</BodyText>
+            </Grid>
+        );
+    }
+    return (
+        <>
+            {teamMembers.map((teamMember) => (
+                <Grid item container spacing={2} alignItems="center" key={teamMember.id}>
+                    <Grid item>
+                        <Avatar
+                            sx={{
+                                backgroundColor: 'primary.light',
+                                height: 32,
+                                width: 32,
+                                fontSize: '16px',
+                            }}
+                        >
+                            {teamMember.user.first_name[0]}
+                            {teamMember.user.last_name[0]}
+                        </Avatar>
+                    </Grid>
+                    <Grid item>
+                        <BodyText>
+                            {teamMember.user.first_name} {teamMember.user.last_name}
+                        </BodyText>
+                        <BodyText>{teamMember.user.main_role}</BodyText>
+                    </Grid>
+                </Grid>
+            ))}
+        </>
+    );
+};
+
+const TeamMemberListSkeleton = () => {
+    return (
+        <>
+            {Array.from({ length: 3 }).map((_, index) => (
+                <Grid item container spacing={2} alignItems="center" key={index}>
+                    <Grid item>
+                        <Skeleton variant="circular" width={32} height={32} />
+                    </Grid>
+                    <Grid item>
+                        <Skeleton variant="text" width={100} />
+                    </Grid>
+                </Grid>
+            ))}
+        </>
+    );
+};
diff --git a/met-web/src/components/engagement/admin/view/StatusChip.tsx b/met-web/src/components/engagement/admin/view/StatusChip.tsx
new file mode 100644
index 000000000..e373b7df3
--- /dev/null
+++ b/met-web/src/components/engagement/admin/view/StatusChip.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import { Chip, Box } from '@mui/material';
+import { useAsyncValue } from 'react-router-dom';
+import { Engagement } from 'models/engagement';
+import { EngagementStatus } from 'constants/engagementStatus';
+
+export const StatusChip = ({
+    status,
+    children,
+}: {
+    status: 'success' | 'warning' | 'danger' | 'info';
+    children: React.ReactNode;
+}) => {
+    return (
+        <Chip
+            label={children}
+            sx={{
+                backgroundColor: `notification.${status}.shade`,
+                color: 'primary.contrastText',
+                borderRadius: '8px',
+            }}
+        />
+    );
+};
+
+export const AutoEngagementStatusChip = () => {
+    const engagement = useAsyncValue() as Engagement;
+    const statusName = EngagementStatus[engagement?.status_id];
+    let status = 'danger' as 'success' | 'warning' | 'danger' | 'info';
+    if (statusName === 'Scheduled') {
+        status = 'info';
+    } else if (statusName === 'Published') {
+        status = 'success';
+    }
+    return <StatusChip status={status}>{engagement?.status_id}</StatusChip>;
+};
diff --git a/met-web/src/components/engagement/admin/view/index.tsx b/met-web/src/components/engagement/admin/view/index.tsx
index c8cd3aac7..10f960fb3 100644
--- a/met-web/src/components/engagement/admin/view/index.tsx
+++ b/met-web/src/components/engagement/admin/view/index.tsx
@@ -1,16 +1,29 @@
-import React, { Suspense } from 'react';
-import { useLoaderData, Await } from 'react-router-dom';
+import React, { Suspense, useState } from 'react';
+import { useRouteLoaderData, Await } from 'react-router-dom';
 import { Engagement } from 'models/engagement';
 import { AutoBreadcrumbs } from 'components/common/Navigation/Breadcrumb';
 import { EngagementStatus } from 'constants/engagementStatus';
-import { Theme, useMediaQuery } from '@mui/material';
+import { Tab } from '@mui/material';
+import { ResponsiveContainer } from 'components/common/Layout';
+import { ConfigSummary } from './ConfigSummary';
+import { TabContext, TabList, TabPanel } from '@mui/lab';
+import { EngagementLoaderData } from 'components/engagement/public/view';
 
 export const AdminEngagementView = () => {
-    const { engagement } = useLoaderData() as { engagement: Promise<Engagement> };
-    const isMediumScreenOrLarger: boolean = useMediaQuery((theme: Theme) => theme.breakpoints.up('md'));
+    const { engagement, teamMembers, slug } = useRouteLoaderData('single-engagement') as EngagementLoaderData;
+
+    const EngagementViewTabs = {
+        config: 'Configuration',
+        author: 'Authoring',
+        activity: 'Activity',
+        results: 'Results',
+        publish: 'Publishing',
+    };
+
+    const [currentTab, setCurrentTab] = useState(EngagementViewTabs.config);
 
     return (
-        <div style={{ marginTop: '3.125rem', padding: isMediumScreenOrLarger ? '0' : '0  1rem' }}>
+        <ResponsiveContainer>
             <AutoBreadcrumbs />
             <Suspense>
                 <Await resolve={engagement}>
@@ -32,6 +45,66 @@ export const AdminEngagementView = () => {
                     )}
                 </Await>
             </Suspense>
-        </div>
+            <TabContext value={currentTab}>
+                <TabList
+                    variant="scrollable"
+                    onChange={(e, newValue) => setCurrentTab(newValue)}
+                    aria-label="Admin Engagement View Tabs"
+                    TabIndicatorProps={{ sx: { display: 'none' } }}
+                    sx={{
+                        '& .MuiTabs-flexContainer': {
+                            justifyContent: 'flex-start',
+                            width: 'max-content',
+                        },
+                    }}
+                >
+                    {Object.entries(EngagementViewTabs).map(([key, value]) => (
+                        <Tab
+                            key={key}
+                            value={value}
+                            label={value}
+                            disableFocusRipple
+                            sx={{
+                                display: 'flex',
+                                justifyContent: 'center',
+                                alignItems: 'center',
+                                height: '48px',
+                                padding: '4px 24px 2px 18px',
+                                fontSize: '14px',
+                                borderRadius: '0px 16px 0px 0px',
+                                borderBottom: '2px solid',
+                                borderBottomColor: 'gray.60',
+                                boxShadow:
+                                    '0px 1px 5px 0px rgba(0, 0, 0, 0.12), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.20)',
+                                backgroundColor: 'gray.10',
+                                color: 'text.secondary',
+                                fontWeight: 'normal',
+                                '&.Mui-selected': {
+                                    backgroundColor: 'primary.main',
+                                    borderColor: 'primary.main',
+                                    color: 'white',
+                                    fontWeight: 'bold',
+                                },
+                                outlineOffset: '-4px',
+                                '&:focus-visible': {
+                                    outline: `2px solid`,
+                                    outlineColor: 'focus.inner',
+                                    border: '4px solid',
+                                    borderColor: 'focus.outer',
+                                    padding: '0px 20px 0px 14px',
+                                },
+                            }}
+                        />
+                    ))}
+                </TabList>
+                <Suspense>
+                    <TabPanel value={EngagementViewTabs.config}>
+                        <Await resolve={Promise.all([engagement, teamMembers, slug])}>
+                            <ConfigSummary />
+                        </Await>
+                    </TabPanel>
+                </Suspense>
+            </TabContext>
+        </ResponsiveContainer>
     );
 };
diff --git a/met-web/src/components/engagement/public/view/EngagementLoader.tsx b/met-web/src/components/engagement/public/view/EngagementLoader.tsx
index 05f3a3a46..792fea0d0 100644
--- a/met-web/src/components/engagement/public/view/EngagementLoader.tsx
+++ b/met-web/src/components/engagement/public/view/EngagementLoader.tsx
@@ -8,19 +8,38 @@ import { getEngagementIdBySlug, getSlugByEngagementId } from 'services/engagemen
 import { getSummaryContent } from 'services/engagementSummaryService';
 import { getWidgets } from 'services/widgetService';
 import { getEngagementMetadata, getMetadataTaxa } from 'services/engagementMetadataService';
+import { Engagement, EngagementMetadata } from 'models/engagement';
+import { Widget } from 'models/widget';
+import { EngagementContent } from 'models/engagementContent';
+import { TaxonType } from 'components/metadataManagement/types';
+import { getTeamMembers } from 'services/membershipService';
+import { EngagementTeamMember } from 'models/engagementTeamMember';
+
+export type EngagementLoaderData = {
+    engagement: Promise<Engagement>;
+    slug: Promise<string>;
+    widgets: Promise<Widget[]>;
+    content: Promise<EngagementContent[]>;
+    contentSummary: Promise<EngagementSummaryContent[]>;
+    metadata: Promise<EngagementMetadata[]>;
+    taxa: Promise<TaxonType[]>;
+    customContent: Promise<EngagementCustomContent[]>;
+    teamMembers: Promise<EngagementTeamMember[]>;
+};
 
 export const engagementLoader = async ({ params }: { params: Params<string> }) => {
-    let { slug } = params;
-    const { engagementId } = params;
-    if (!slug && engagementId) {
-        const response = await getSlugByEngagementId(Number(engagementId));
-        slug = response.slug;
-    }
-    const engagement = getEngagementIdBySlug(slug ?? '').then((response) => getEngagement(response.engagement_id));
+    const { slug: slugParam, engagementId } = params;
+    const slug = slugParam
+        ? Promise.resolve(slugParam)
+        : getSlugByEngagementId(Number(engagementId)).then((response) => response.slug);
+    const engagement = slugParam
+        ? getEngagementIdBySlug(slugParam).then((response) => getEngagement(response.engagement_id))
+        : getEngagement(Number(engagementId));
     const widgets = engagement.then((response) => getWidgets(Number(response.id)));
     const content = engagement.then((response) => getEngagementContent(response.id));
     const engagementMetadata = engagement.then((response) => getEngagementMetadata(Number(response.id)));
     const taxaData = getMetadataTaxa();
+    const teamMembers = engagement.then((response) => getTeamMembers({ engagement_id: response.id }));
 
     const metadata = engagementMetadata.then((metaResponse) => {
         taxaData.then((taxaResponse) => {
@@ -75,5 +94,15 @@ export const engagementLoader = async ({ params }: { params: Params<string> }) =
         return finishedContent;
     };
 
-    return defer({ engagement, slug, widgets, content, contentSummary, metadata, taxa, customContent });
+    return defer({
+        engagement,
+        slug,
+        widgets,
+        content,
+        contentSummary,
+        metadata,
+        taxa,
+        customContent,
+        teamMembers,
+    });
 };
diff --git a/met-web/src/components/engagement/public/view/index.tsx b/met-web/src/components/engagement/public/view/index.tsx
index f59f39267..812d5bfc9 100644
--- a/met-web/src/components/engagement/public/view/index.tsx
+++ b/met-web/src/components/engagement/public/view/index.tsx
@@ -20,4 +20,5 @@ export const PublicEngagementView = () => {
 export default PublicEngagementView;
 
 export { engagementLoader } from './EngagementLoader';
+export type { EngagementLoaderData } from './EngagementLoader';
 export { engagementListLoader } from './EngagementListLoader';
diff --git a/met-web/src/routes/AuthenticatedRoutes.tsx b/met-web/src/routes/AuthenticatedRoutes.tsx
index 8179d4dfc..043b0f2a8 100644
--- a/met-web/src/routes/AuthenticatedRoutes.tsx
+++ b/met-web/src/routes/AuthenticatedRoutes.tsx
@@ -34,10 +34,12 @@ import { Engagement } from 'models/engagement';
 import { getAllTenants, getTenant } from 'services/tenantService';
 import { engagementLoader, engagementListLoader } from 'components/engagement/public/view';
 import { SurveyLoader } from 'components/survey/building/SurveyLoader';
-import { languageLoader } from 'components/engagement/admin/create/languageLoader';
+import { languageLoader } from 'components/engagement/admin/config/LanguageLoader';
 import { userSearchLoader } from 'components/userManagement/userSearchLoader';
-import EngagementCreationWizard from 'components/engagement/admin/create';
-import engagementCreateAction from 'components/engagement/admin/create/engagementCreateAction';
+import EngagementCreationWizard from 'components/engagement/admin/config/wizard/CreationWizard';
+import engagementCreateAction from 'components/engagement/admin/config/EngagementCreateAction';
+import EngagementConfigurationWizard from 'components/engagement/admin/config/wizard/ConfigWizard';
+import engagementUpdateAction from 'components/engagement/admin/config/EngagementUpdateAction';
 
 const AuthenticatedRoutes = () => {
     return (
@@ -79,7 +81,6 @@ const AuthenticatedRoutes = () => {
                         path="wizard"
                         handle={{ crumb: () => ({ name: 'New Engagement' }) }}
                         element={<EngagementCreationWizard />}
-                        loader={languageLoader}
                     />
                 </Route>
                 <Route
@@ -87,26 +88,32 @@ const AuthenticatedRoutes = () => {
                     id="single-engagement"
                     errorElement={<NotFound />}
                     loader={engagementLoader}
+                    handle={{
+                        crumb: async (data: { engagement: Promise<Engagement> }) => {
+                            return data.engagement.then((engagement) => {
+                                return {
+                                    link: `/engagements/${engagement.id}/view`,
+                                    name: engagement.name,
+                                };
+                            });
+                        },
+                    }}
                 >
                     <Route element={<AuthGate allowedRoles={[USER_ROLES.EDIT_ENGAGEMENT]} />}>
                         <Route path="form" element={<EngagementForm />} />
+                        <Route
+                            path="config"
+                            element={<EngagementConfigurationWizard />}
+                            action={engagementUpdateAction}
+                            handle={{
+                                crumb: () => ({
+                                    name: 'Configure',
+                                }),
+                            }}
+                        />
                     </Route>
                     <Route path="old-view" element={<OldEngagementView />} />
-                    <Route
-                        path="view"
-                        loader={engagementLoader}
-                        handle={{
-                            crumb: async (data: { engagement: Promise<Engagement> }) => {
-                                return data.engagement.then((engagement) => {
-                                    return {
-                                        link: `/engagements/${engagement.id}/view`,
-                                        name: engagement.name,
-                                    };
-                                });
-                            },
-                        }}
-                        element={<AdminEngagementView />}
-                    />
+                    <Route path="view" element={<AdminEngagementView />} />
                     <Route path="comments/:dashboardType" element={<EngagementComments />} />
                     <Route path="dashboard/:dashboardType" element={<PublicDashboard />} />
                 </Route>
@@ -117,7 +124,7 @@ const AuthenticatedRoutes = () => {
                 </Route>
             </Route>
             <Route path="/metadatamanagement" element={<MetadataManagement />} />
-            <Route path="/languages" element={<Language />} />
+            <Route path="/languages" element={<Language />} loader={languageLoader} />
             <Route
                 id="tenant-admin"
                 path="/tenantadmin"
diff --git a/met-web/src/services/engagementService/types.ts b/met-web/src/services/engagementService/types.ts
index 69417207a..cc7b596de 100644
--- a/met-web/src/services/engagementService/types.ts
+++ b/met-web/src/services/engagementService/types.ts
@@ -38,4 +38,5 @@ export interface PatchEngagementRequest {
     rich_description?: string;
     banner_filename?: string;
     status_block?: unknown[];
+    is_internal?: boolean;
 }
diff --git a/met-web/src/styles/Theme.ts b/met-web/src/styles/Theme.ts
index 2530908ed..edaf729dd 100644
--- a/met-web/src/styles/Theme.ts
+++ b/met-web/src/styles/Theme.ts
@@ -128,6 +128,8 @@ export const colors = {
 };
 
 export const Palette = {
+    ...colors.surface,
+    notification: colors.notification,
     primary: {
         main: colors.surface.blue[90],
         light: colors.surface.blue[80],
@@ -147,6 +149,7 @@ export const Palette = {
     },
     text: {
         primary: colors.type.regular.primary,
+        secondary: colors.type.regular.secondary,
     },
     action: {
         active: colors.type.regular.link,
@@ -154,6 +157,10 @@ export const Palette = {
     info: {
         main: colors.surface.gray[90],
     },
+    focus: {
+        outer: colors.focus.regular.outer,
+        inner: colors.focus.regular.inner,
+    },
     internalHeader: {
         backgroundColor: colors.surface.white,
         color: colors.type.regular.primary,
@@ -187,27 +194,7 @@ export const Palette = {
 };
 
 export const BaseTheme = createTheme({
-    palette: {
-        primary: {
-            main: Palette.primary.main,
-            light: Palette.primary.light,
-            dark: Palette.primary.dark,
-        },
-        secondary: {
-            main: Palette.secondary.main,
-            dark: Palette.secondary.dark,
-            light: Palette.secondary.light,
-        },
-        text: {
-            primary: Palette.text.primary,
-        },
-        action: {
-            active: Palette.action.active,
-        },
-        info: {
-            main: Palette.info.main,
-        },
-    },
+    palette: Palette,
     components: {
         MuiPaper: {
             styleOverrides: {
@@ -322,6 +309,8 @@ export const BaseTheme = createTheme({
 
 export const DarkPalette = {
     mode: 'dark' as PaletteMode,
+    ...colors.surface,
+    notification: colors.notification,
     primary: {
         main: colors.surface.white,
         light: colors.surface.blue[90],
@@ -343,6 +332,7 @@ export const DarkPalette = {
     },
     text: {
         primary: colors.type.inverted.primary,
+        secondary: colors.type.inverted.secondary,
     },
     action: {
         active: colors.type.inverted.link,

From ecfb917d8624a9fef011bc92c9cb269b6ced1f66 Mon Sep 17 00:00:00 2001
From: NatSquared <nat.k.weiland@gmail.com>
Date: Thu, 15 Aug 2024 17:18:16 -0700
Subject: [PATCH 2/8] Merge cleanup + lint

---
 .../admin/config/LanguageManager.tsx          |  2 +-
 .../engagement/admin/config/wizard/index.tsx  |  8 ++---
 .../engagement/admin/view/AuthoringTab.tsx    |  4 +--
 .../engagement/admin/view/ConfigSummary.tsx   |  4 +--
 .../engagement/admin/view/StatusChip.tsx      | 36 -------------------
 5 files changed, 8 insertions(+), 46 deletions(-)
 delete mode 100644 met-web/src/components/engagement/admin/view/StatusChip.tsx

diff --git a/met-web/src/components/engagement/admin/config/LanguageManager.tsx b/met-web/src/components/engagement/admin/config/LanguageManager.tsx
index d334800e3..faa558088 100644
--- a/met-web/src/components/engagement/admin/config/LanguageManager.tsx
+++ b/met-web/src/components/engagement/admin/config/LanguageManager.tsx
@@ -1,7 +1,7 @@
 import React, { useEffect } from 'react';
 import { Box, FormControlLabel, Grid, Radio, RadioGroup, TextField } from '@mui/material';
 import { textInputStyles } from 'components/common/Input/TextInput';
-import { useAsyncValue, useFetcher } from 'react-router-dom';
+import { useFetcher } from 'react-router-dom';
 import { BodyText } from 'components/common/Typography';
 import { When } from 'react-if';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
diff --git a/met-web/src/components/engagement/admin/config/wizard/index.tsx b/met-web/src/components/engagement/admin/config/wizard/index.tsx
index e9ccefd4b..3be10278e 100644
--- a/met-web/src/components/engagement/admin/config/wizard/index.tsx
+++ b/met-web/src/components/engagement/admin/config/wizard/index.tsx
@@ -1,8 +1,8 @@
-import React, { Suspense, useState } from 'react';
-import { Header1, Header2 } from 'components/common/Typography';
+import React, { useState } from 'react';
+import { Header2 } from 'components/common/Typography';
 import { Button, TextField } from 'components/common/Input';
-import { Form, useLoaderData, Await } from 'react-router-dom';
-import { Box, Skeleton } from '@mui/material';
+import { Form } from 'react-router-dom';
+import { Box } from '@mui/material';
 import { Dayjs } from 'dayjs';
 import { Controller, useFormContext } from 'react-hook-form';
 import EngagementVisibilityControl from '../EngagementVisibilityControl';
diff --git a/met-web/src/components/engagement/admin/view/AuthoringTab.tsx b/met-web/src/components/engagement/admin/view/AuthoringTab.tsx
index 8f81689fa..4ada78d1c 100644
--- a/met-web/src/components/engagement/admin/view/AuthoringTab.tsx
+++ b/met-web/src/components/engagement/admin/view/AuthoringTab.tsx
@@ -159,7 +159,7 @@ export const AuthoringTab = () => {
             <Header2 decorated>Authoring</Header2>
             <MetHeader3 style={metHeaderStyles}>Page Section Authoring</MetHeader3>
             <When condition={!requiredSectionsCompleted}>
-                <SystemMessage sx={systemMessageStyles} status="error">
+                <SystemMessage sx={systemMessageStyles} status="danger">
                     There are incomplete or missing sections of required content in your engagement. Please complete all
                     required content in all of the languages included in your engagement.
                 </SystemMessage>
@@ -191,7 +191,7 @@ export const AuthoringTab = () => {
             <Grid container direction="column" id="feedback-container" sx={{ ...anchorContainerStyles }}>
                 <MetHeader3 style={metHeaderStyles}>Feedback Configuration</MetHeader3>
                 <When condition={!feedbackCompleted}>
-                    <SystemMessage sx={systemMessageStyles} status="error">
+                    <SystemMessage sx={systemMessageStyles} status="danger">
                         There are feedback methods included in your engagement that are incomplete. Please complete
                         configuration for all of the feedback methods included in your engagement.
                     </SystemMessage>
diff --git a/met-web/src/components/engagement/admin/view/ConfigSummary.tsx b/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
index 46bee0a24..12031d1bf 100644
--- a/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
+++ b/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
@@ -6,16 +6,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faCopy } from '@fortawesome/pro-light-svg-icons';
 import { globalFocusVisible } from 'components/common';
 import { getBaseUrl } from 'helper';
-import { Await, useAsyncValue, useRouteLoaderData } from 'react-router-dom';
+import { Await, useAsyncValue } from 'react-router-dom';
 import { Engagement } from 'models/engagement';
 import { EngagementStatusChip } from 'components/common/Indicators';
 import { SubmissionStatus } from 'constants/engagementStatus';
 import dayjs from 'dayjs';
-import { EngagementLoaderData } from 'components/engagement/public/view';
 import { ENGAGEMENT_MEMBERSHIP_STATUS, EngagementTeamMember } from 'models/engagementTeamMember';
 import { Button } from 'components/common/Input';
 import { faPen } from '@fortawesome/pro-regular-svg-icons';
-import { SystemMessage } from 'components/common/Layout/SystemMessage';
 
 export const ConfigSummary = () => {
     const siteUrl = getBaseUrl();
diff --git a/met-web/src/components/engagement/admin/view/StatusChip.tsx b/met-web/src/components/engagement/admin/view/StatusChip.tsx
deleted file mode 100644
index e373b7df3..000000000
--- a/met-web/src/components/engagement/admin/view/StatusChip.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from 'react';
-import { Chip, Box } from '@mui/material';
-import { useAsyncValue } from 'react-router-dom';
-import { Engagement } from 'models/engagement';
-import { EngagementStatus } from 'constants/engagementStatus';
-
-export const StatusChip = ({
-    status,
-    children,
-}: {
-    status: 'success' | 'warning' | 'danger' | 'info';
-    children: React.ReactNode;
-}) => {
-    return (
-        <Chip
-            label={children}
-            sx={{
-                backgroundColor: `notification.${status}.shade`,
-                color: 'primary.contrastText',
-                borderRadius: '8px',
-            }}
-        />
-    );
-};
-
-export const AutoEngagementStatusChip = () => {
-    const engagement = useAsyncValue() as Engagement;
-    const statusName = EngagementStatus[engagement?.status_id];
-    let status = 'danger' as 'success' | 'warning' | 'danger' | 'info';
-    if (statusName === 'Scheduled') {
-        status = 'info';
-    } else if (statusName === 'Published') {
-        status = 'success';
-    }
-    return <StatusChip status={status}>{engagement?.status_id}</StatusChip>;
-};

From 57fdc0c0a8ca4bd607a9706336512657b58c2e74 Mon Sep 17 00:00:00 2001
From: NatSquared <nat.k.weiland@gmail.com>
Date: Thu, 15 Aug 2024 17:31:12 -0700
Subject: [PATCH 3/8] Fix tab styles (accidentally overridden in merge)

---
 .../engagement/admin/view/index.tsx           | 24 ++++++++++++++-----
 1 file changed, 18 insertions(+), 6 deletions(-)

diff --git a/met-web/src/components/engagement/admin/view/index.tsx b/met-web/src/components/engagement/admin/view/index.tsx
index fb02c822b..c02056bba 100644
--- a/met-web/src/components/engagement/admin/view/index.tsx
+++ b/met-web/src/components/engagement/admin/view/index.tsx
@@ -65,23 +65,35 @@ export const AdminEngagementView = () => {
                             key={key}
                             value={value}
                             label={value}
+                            disableFocusRipple
                             sx={{
                                 display: 'flex',
-                                height: '48px',
-                                padding: '0px 24px 0px 18px',
                                 justifyContent: 'center',
                                 alignItems: 'center',
+                                height: '48px',
+                                padding: '4px 24px 2px 18px',
+                                fontSize: '14px',
                                 borderRadius: '0px 16px 0px 0px',
-                                borderBottom: '1px solid',
-                                borderColor: 'gray.60',
-                                backgroundColor: 'gray.10',
-                                color: 'text.secondary',
+                                borderBottom: '2px solid',
+                                borderBottomColor: 'gray.60',
                                 boxShadow:
                                     '0px 1px 5px 0px rgba(0, 0, 0, 0.12), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.20)',
+                                backgroundColor: 'gray.10',
+                                color: 'text.secondary',
+                                fontWeight: 'normal',
                                 '&.Mui-selected': {
                                     backgroundColor: 'primary.main',
                                     borderColor: 'primary.main',
                                     color: 'white',
+                                    fontWeight: 'bold',
+                                },
+                                outlineOffset: '-4px',
+                                '&:focus-visible': {
+                                    outline: `2px solid`,
+                                    outlineColor: 'focus.inner',
+                                    border: '4px solid',
+                                    borderColor: 'focus.outer',
+                                    padding: '0px 20px 0px 14px',
                                 },
                             }}
                         />

From 0c67821669e6ea68027955d44d3f60d1b003d475 Mon Sep 17 00:00:00 2001
From: NatSquared <nat.k.weiland@gmail.com>
Date: Thu, 15 Aug 2024 17:34:26 -0700
Subject: [PATCH 4/8] Fix SonarCloud issues

---
 .../engagement/admin/config/EngagementUpdateAction.tsx   | 9 ++++-----
 .../engagement/admin/config/LanguageManager.tsx          | 1 -
 .../components/engagement/admin/view/ConfigSummary.tsx   | 2 +-
 3 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/met-web/src/components/engagement/admin/config/EngagementUpdateAction.tsx b/met-web/src/components/engagement/admin/config/EngagementUpdateAction.tsx
index a560ad13b..b1f6df846 100644
--- a/met-web/src/components/engagement/admin/config/EngagementUpdateAction.tsx
+++ b/met-web/src/components/engagement/admin/config/EngagementUpdateAction.tsx
@@ -42,12 +42,11 @@ export const engagementUpdateAction: ActionFunction = async ({ request, params }
                     // If the user was previously deactivated, reinstate them
                     reinstateMembership(engagementId, member.user_id);
                 }
-            } else {
-                if (!isUserInForm) {
-                    // If the user was previously active but is not in the form, revoke their membership
-                    revokeMembership(engagementId, member.user_id);
-                }
+            } else if (!isUserInForm) {
+                // If the user was previously active but is not in the form, revoke their membership
+                revokeMembership(engagementId, member.user_id);
             }
+
             // Remove all known users from the set so we can add new members in the next step
             usersSet.delete(String(member.user.external_id));
         }
diff --git a/met-web/src/components/engagement/admin/config/LanguageManager.tsx b/met-web/src/components/engagement/admin/config/LanguageManager.tsx
index faa558088..74dc49f37 100644
--- a/met-web/src/components/engagement/admin/config/LanguageManager.tsx
+++ b/met-web/src/components/engagement/admin/config/LanguageManager.tsx
@@ -23,7 +23,6 @@ export const LanguageManager = () => {
     const engagementForm = useFormContext();
     const { setValue, watch } = engagementForm;
     const selectedLanguages = watch('languages') as Language[];
-    // const [isSingleLanguage, setIsSingleLanguage] = React.useState<boolean | null>(null);
     const fetcher = useFetcher();
     const fetcherData = fetcher.data as { languages: Language[] } | undefined;
     const { languages: availableLanguages } = fetcherData ?? { languages: [] };
diff --git a/met-web/src/components/engagement/admin/view/ConfigSummary.tsx b/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
index 12031d1bf..7098d95ad 100644
--- a/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
+++ b/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
@@ -227,7 +227,7 @@ const TeamMemberListSkeleton = () => {
     return (
         <>
             {Array.from({ length: 3 }).map((_, index) => (
-                <Grid item container spacing={2} alignItems="center" key={index}>
+                <Grid item container spacing={2} alignItems="center" key={`static-${index}`}>
                     <Grid item>
                         <Skeleton variant="circular" width={32} height={32} />
                     </Grid>

From d3d3862c63128e1f6d0743c86cc05957e60e29ea Mon Sep 17 00:00:00 2001
From: NatSquared <nat.k.weiland@gmail.com>
Date: Thu, 15 Aug 2024 17:42:57 -0700
Subject: [PATCH 5/8] sonarcloud pls

---
 met-web/src/components/engagement/admin/view/ConfigSummary.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/met-web/src/components/engagement/admin/view/ConfigSummary.tsx b/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
index 7098d95ad..d1528bc75 100644
--- a/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
+++ b/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
@@ -227,7 +227,7 @@ const TeamMemberListSkeleton = () => {
     return (
         <>
             {Array.from({ length: 3 }).map((_, index) => (
-                <Grid item container spacing={2} alignItems="center" key={`static-${index}`}>
+                <Grid item container spacing={2} alignItems="center">
                     <Grid item>
                         <Skeleton variant="circular" width={32} height={32} />
                     </Grid>

From 0112a7c21ec298cb18b6ba098e26d3d3ab603fde Mon Sep 17 00:00:00 2001
From: NatSquared <nat.k.weiland@gmail.com>
Date: Thu, 15 Aug 2024 17:47:21 -0700
Subject: [PATCH 6/8] >:(

---
 .../src/components/engagement/admin/view/ConfigSummary.tsx    | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/met-web/src/components/engagement/admin/view/ConfigSummary.tsx b/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
index d1528bc75..68bbdd1c8 100644
--- a/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
+++ b/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
@@ -226,8 +226,8 @@ const TeamMemberList = () => {
 const TeamMemberListSkeleton = () => {
     return (
         <>
-            {Array.from({ length: 3 }).map((_, index) => (
-                <Grid item container spacing={2} alignItems="center">
+            {[1, 2, 3].map((value) => (
+                <Grid item container spacing={2} alignItems="center" key={value}>
                     <Grid item>
                         <Skeleton variant="circular" width={32} height={32} />
                     </Grid>

From 758567f3048f4bd6f699130c92043e461d5413de Mon Sep 17 00:00:00 2001
From: NatSquared <nat.k.weiland@gmail.com>
Date: Thu, 15 Aug 2024 18:11:38 -0700
Subject: [PATCH 7/8] remove custom panel styling

---
 met-web/src/components/engagement/admin/view/index.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/met-web/src/components/engagement/admin/view/index.tsx b/met-web/src/components/engagement/admin/view/index.tsx
index c02056bba..6455e998f 100644
--- a/met-web/src/components/engagement/admin/view/index.tsx
+++ b/met-web/src/components/engagement/admin/view/index.tsx
@@ -106,7 +106,7 @@ export const AdminEngagementView = () => {
                         </Await>
                     </TabPanel>
                 </Suspense>
-                <TabPanel value={EngagementViewTabs.author} style={{ paddingLeft: '0', paddingRight: '0' }}>
+                <TabPanel value={EngagementViewTabs.author}>
                     <Await resolve={engagement}>
                         <AuthoringTab />
                     </Await>

From 993d964dff473196c6a396931c356d0fc8dd9d6f Mon Sep 17 00:00:00 2001
From: NatSquared <nat.k.weiland@gmail.com>
Date: Mon, 19 Aug 2024 09:54:28 -0700
Subject: [PATCH 8/8] Wrap engagement URL copy button in LiveAnnouncer for
 accessibility, add index route

---
 .../engagement/admin/view/ConfigSummary.tsx   | 67 ++++++++++---------
 met-web/src/routes/AuthenticatedRoutes.tsx    |  1 +
 2 files changed, 37 insertions(+), 31 deletions(-)

diff --git a/met-web/src/components/engagement/admin/view/ConfigSummary.tsx b/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
index 68bbdd1c8..d24a50906 100644
--- a/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
+++ b/met-web/src/components/engagement/admin/view/ConfigSummary.tsx
@@ -14,6 +14,7 @@ import dayjs from 'dayjs';
 import { ENGAGEMENT_MEMBERSHIP_STATUS, EngagementTeamMember } from 'models/engagementTeamMember';
 import { Button } from 'components/common/Input';
 import { faPen } from '@fortawesome/pro-regular-svg-icons';
+import { LiveAnnouncer, LiveMessage } from 'react-aria-live';
 
 export const ConfigSummary = () => {
     const siteUrl = getBaseUrl();
@@ -44,37 +45,41 @@ export const ConfigSummary = () => {
                                 </BodyText>
                             </Grid>
                             <Grid item>
-                                <Tooltip arrow open={tooltipOpen} title="Copied!" placement="top">
-                                    <IconButton
-                                        size="small"
-                                        sx={{
-                                            backgroundColor: 'primary.light',
-                                            color: 'white',
-                                            width: '32px',
-                                            height: '32px',
-                                            '&:hover': {
-                                                backgroundColor: 'primary.main',
-                                            },
-                                            ...globalFocusVisible,
-                                            display: 'inline-block',
-                                            marginRight: '0.5rem',
-                                        }}
-                                        onClick={() => {
-                                            navigator.clipboard.writeText(`${siteUrl}/${slug}`);
-                                            setTooltipOpen(true);
-                                        }}
-                                    >
-                                        <FontAwesomeIcon
-                                            fontSize={16}
-                                            icon={faCopy}
-                                            style={{ position: 'relative', bottom: '4px' }}
-                                        />
-                                    </IconButton>
-                                </Tooltip>
-                                <BodyText sx={{ display: 'inline' }}>
-                                    <span style={{ fontWeight: 'bold' }}>{siteUrl}/</span>
-                                    {slug}
-                                </BodyText>
+                                <LiveAnnouncer>
+                                    <LiveMessage aria-live="assertive" message={tooltipOpen ? 'Copied!' : ''} />
+                                    <Tooltip arrow open={tooltipOpen} title="Copied!" placement="top">
+                                        <IconButton
+                                            size="small"
+                                            sx={{
+                                                backgroundColor: 'primary.light',
+                                                color: 'white',
+                                                width: '32px',
+                                                height: '32px',
+                                                '&:hover': {
+                                                    backgroundColor: 'primary.main',
+                                                },
+                                                ...globalFocusVisible,
+                                                display: 'inline-block',
+                                                marginRight: '0.5rem',
+                                            }}
+                                            onClick={() => {
+                                                navigator.clipboard.writeText(`${siteUrl}/${slug}`);
+                                                setTooltipOpen(true);
+                                            }}
+                                            aria-label="Press enter to copy engagement URL to clipboard"
+                                        >
+                                            <FontAwesomeIcon
+                                                fontSize={16}
+                                                icon={faCopy}
+                                                style={{ position: 'relative', bottom: '4px' }}
+                                            />
+                                        </IconButton>
+                                    </Tooltip>
+                                    <BodyText sx={{ display: 'inline' }}>
+                                        <span style={{ fontWeight: 'bold' }}>{siteUrl}/</span>
+                                        {slug}
+                                    </BodyText>
+                                </LiveAnnouncer>
                             </Grid>
                         </Grid>
                     </OutlineBox>
diff --git a/met-web/src/routes/AuthenticatedRoutes.tsx b/met-web/src/routes/AuthenticatedRoutes.tsx
index 043b0f2a8..fcf94f828 100644
--- a/met-web/src/routes/AuthenticatedRoutes.tsx
+++ b/met-web/src/routes/AuthenticatedRoutes.tsx
@@ -99,6 +99,7 @@ const AuthenticatedRoutes = () => {
                         },
                     }}
                 >
+                    <Route index element={<Navigate to="view" />} />
                     <Route element={<AuthGate allowedRoles={[USER_ROLES.EDIT_ENGAGEMENT]} />}>
                         <Route path="form" element={<EngagementForm />} />
                         <Route