Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/deseng668: Added authoring side nav and bottom nav, modified routing, added authoring context, added base page for banner #2581

Merged
merged 12 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## August 23, 2024
- **Feature** New authoring content section [🎟️ DESENG-668](https://citz-gdx.atlassian.net/browse/DESENG-668)
- Implemented authoring side nav
- Implemented authoring bottom nav
- Implemented authoring section context
- Still working on skeletons

## August 21, 2024

- **Feature** Reusable widget component [🎟️ DESENG-675](https://citz-gdx.atlassian.net/browse/DESENG-675)
Expand Down
Binary file added met-web/src/assets/images/pagePreview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const engagementUpdateAction: ActionFunction = async ({ request, params }
console.error('Error updating team members', e);
}

return redirect(`/engagements/${engagementId}/view`);
return redirect(`/engagements/${engagementId}/details/config`);
};

export default engagementUpdateAction;
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const ConfigForm = () => {
}),
{
method: 'patch',
action: `/engagements/${engagement.id}/config/`,
action: `/engagements/${engagement.id}/details/config/edit`,
},
);
};
Expand Down Expand Up @@ -131,7 +131,7 @@ const ConfigForm = () => {
>
<Grid container direction="row" item xs={12}>
<Grid xs={12}>
<Header2 sx={{ mb: 0 }}>We're just looking over your configuration.</Header2>
<Header2 sx={{ mb: 0 }}>We're saving your configuration.</Header2>
</Grid>
</Grid>
<Grid container direction="row" item xs={12}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { useOutletContext, Form, useParams } from 'react-router-dom';
import AuthoringBottomNav from './AuthoringBottomNav';
import { EngagementUpdateData } from './AuthoringContext';
import { useFormContext } from 'react-hook-form';
import UnsavedWorkConfirmation from 'components/common/Navigation/UnsavedWorkConfirmation';
import { Box } from '@mui/material';
import { AuthoringContextType } from './types';

const AuthoringBanner = () => {
const { onSubmit }: AuthoringContextType = useOutletContext();
const { engagementId } = useParams() as { engagementId: string };

const {
handleSubmit,
formState: { isDirty, isValid, isSubmitting },
} = useFormContext<EngagementUpdateData>();

return (
<Box>
<Form onSubmit={handleSubmit(onSubmit)}>
<input type="hidden" name="id" value={engagementId} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has no effect... I've made a suggestion to update your AuthoringContext.tsx to use the actual engagement ID

Suggested change
<input type="hidden" name="id" value={engagementId} />

<AuthoringBottomNav isDirty={isDirty} isValid={isValid} isSubmitting={isSubmitting} />
<UnsavedWorkConfirmation blockNavigationWhen={isDirty && !isSubmitting} />
</Form>
</Box>
);
};

export default AuthoringBanner;
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import React, { Suspense, useState, useMemo } from 'react';
import { Await, useAsyncValue, useParams } from 'react-router-dom';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { Await, useAsyncValue, useParams } from 'react-router-dom';
import { Await, useAsyncValue, useLocation, useParams } from 'react-router-dom';

import { AppBar, Theme, ThemeProvider, Box, useMediaQuery, Select, MenuItem, SelectChangeEvent } from '@mui/material';
import { Palette, colors, DarkTheme, BaseTheme } from 'styles/Theme';
import { When, Unless } from 'react-if';
import { BodyText } from 'components/common/Typography';
import { elevations } from 'components/common';
import { Button } from 'components/common/Input';
import { Link } from 'components/common/Navigation';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCheck } from '@fortawesome/pro-regular-svg-icons';
import { useAppSelector } from 'hooks';
import { getAuthoringRoutes } from './AuthoringNavElements';
import { getTenantLanguages } from 'services/languageService';
import { Language } from 'models/language';
import { StatusCircle } from '../../view/AuthoringTab';
import pagePreview from 'assets/images/pagePreview.png';
import { AuthoringBottomNavProps, LanguageSelectorProps } from './types';

const AuthoringBottomNav = (props: AuthoringBottomNavProps) => {
const { isDirty, isValid, isSubmitting } = props;
const isMediumScreenOrLarger = useMediaQuery((theme: Theme) => theme.breakpoints.up('md'));
const padding = { xs: '1rem 1rem', md: '1rem 1.5rem 1rem 2rem', lg: '1rem 3rem 1rem 2rem' };

const tenant = useAppSelector((state) => state.tenant);
const { engagementId } = useParams() as { engagementId: string };
const location = window.location.href;
const locationArray = location.split('/');
const slug = locationArray[locationArray.length - 1];

const languages = useMemo(() => getTenantLanguages(tenant.id), [tenant.id]); // todo: Using tenant language list until language data is integrated with the engagement.
const [currentLanguage, setCurrentLanguage] = useState(useAppSelector((state) => state.language.id));

const getPageValue = () => {
const authoringRoutes = getAuthoringRoutes(Number(engagementId), tenant);
return authoringRoutes.find((route) => route.path.includes(slug))?.name;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const authoringRoutes = getAuthoringRoutes(Number(engagementId), tenant);
return authoringRoutes.find((route) => route.path.includes(slug))?.name;
const authoringRoutes = getAuthoringRoutes(Number(engagementId));
return authoringRoutes.find((route) => route.path === location.pathname)?.name;

};

const getLanguageValue = (currentLanguage: string, languages: Language[]) => {
return languages.find((language) => language.code === currentLanguage)?.name;
};

const buttonStyles = {
height: '2.6rem',
borderRadius: '8px',
border: 'none',
padding: '0 1rem',
minWidth: '8.125rem',
fontSize: '0.9rem',
};

return (
<AppBar
component={'nav'}
position="fixed"
sx={{
backgroundColor: 'transparent',
borderTopRightRadius: '16px',
minHeight: '5rem',
backgroundClip: 'padding-box',
overflow: 'hidden',
top: 'auto',
left: 0,
bottom: 0,
boxShadow: elevations.default,
}}
data-testid="appbar-authoring-bottom-nav"
>
<Box
sx={{
background: colors.surface.blue[90],
minHeight: '5rem',
justifyContent: 'flex-start',
padding: padding,
display: 'flex',
alignItems: 'center',
flexWrap: isMediumScreenOrLarger ? 'nowrap' : 'wrap',
}}
>
<ThemeProvider theme={DarkTheme}>
<Box
sx={{
width: '18.75rem',
minWidth: '18.75rem',
marginBottom: isMediumScreenOrLarger ? '0' : '1rem',
}}
>
<BodyText sx={{ fontWeight: 'bold' }}>Currently Authoring</BodyText>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<BodyText sx={{ fontWeight: 'bold' }}>Currently Authoring</BodyText>
<BodyText bold>Currently Authoring</BodyText>

<BodyText sx={{ fontSize: '0.7rem', alignItems: 'center', marginTop: '-5px', display: 'flex' }}>
<span>{getPageValue()}</span>
<span style={{ fontSize: '0.4rem', paddingLeft: '0.4rem', paddingRight: '0.4rem' }}>
{'\u2B24'}
Comment on lines +75 to +76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a different Unicode character that doesn't require a font size adjustment

Suggested change
<span style={{ fontSize: '0.4rem', paddingLeft: '0.4rem', paddingRight: '0.4rem' }}>
{'\u2B24'}
<span style={{ paddingLeft: '0.5rem', paddingRight: '0.5rem', userSelect: 'none' }}>
{'\u25CF'}

</span>
<Suspense>
<Await resolve={languages}>
{(languages: Language[]) => (
<span>{getLanguageValue(currentLanguage, languages)}</span>
)}
</Await>
</Suspense>
</BodyText>
</Box>
<Box
sx={{
width: '43.75rem',
justifyContent: 'flex-start',
display: 'flex',
}}
>
<ThemeProvider theme={BaseTheme}>
<Suspense>
<Await resolve={languages}>
<LanguageSelector
currentLanguage={currentLanguage}
setCurrentLanguage={setCurrentLanguage}
/>
</Await>
</Suspense>
</ThemeProvider>

<Button
disabled={!isValid || !isDirty || isSubmitting}
type="submit"
name="request_type"
value="update"
sx={{
...buttonStyles,
margin: '0 1.2rem',
}}
>
<Link sx={{ color: colors.surface.gray[60] }} href="#">
Save Section
</Link>
</Button>
<Button
type="submit"
name="request_type"
value="preview"
sx={{
...buttonStyles,
marginLeft: 'auto',
}}
>
<img style={{ paddingRight: '0.3rem' }} src={pagePreview} alt="page preview icon" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move icon to icon prop and remove custom spacing. Also clear alt text because it is redundant to real text nearby: https://www.w3.org/WAI/tutorials/images/decision-tree/

Suggested change
>
<img style={{ paddingRight: '0.3rem' }} src={pagePreview} alt="page preview icon" />
icon={<img src={pagePreview} alt="" />}
>

Preview
</Button>
</Box>
<Box style={{ width: '25rem', display: 'flex' }}></Box>
</ThemeProvider>
</Box>
</AppBar>
);
};

const LanguageSelector = ({ currentLanguage, setCurrentLanguage }: LanguageSelectorProps) => {
const languages = useAsyncValue() as Language[];
const handleSelectChange = (event: SelectChangeEvent<string>) => {
const newLanguageCode = event.target.value;
setCurrentLanguage(newLanguageCode);
};
return (
<Select
value={currentLanguage}
onChange={handleSelectChange}
sx={{
height: '2.6rem',
borderRadius: '8px',
width: '9.375rem',
backgroundColor: colors.surface.gray[10],
border: 'none',
color: Palette.text.primary,
fontSize: '0.9rem',
cursor: 'pointer',
'& .MuiSelect-icon': {
color: Palette.text.primary,
},
}}
renderValue={(value) => {
const completed = false; // todo: Replace with real "completed" boolean value once it is available.
return (
<span>
<When condition={completed}>
<FontAwesomeIcon style={{ marginRight: '0.3rem' }} icon={faCheck} />
</When>
{languages.find((language) => language.code === value)?.name}
<Unless condition={completed}>
<StatusCircle required={true} />
</Unless>
</span>
);
}}
>
{languages.map((language) => {
const completed = false; // todo: Replace with the real "completed" boolean values once they are available.
return (
<MenuItem value={language.code} key={language.code}>
<When condition={completed}>
<FontAwesomeIcon style={{ marginRight: '0.3rem' }} icon={faCheck} />
</When>
{language.name}
<Unless condition={completed}>
<StatusCircle required={true} />
</Unless>
</MenuItem>
);
})}
</Select>
);
};

export default AuthoringBottomNav;
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import { Dayjs } from 'dayjs';
import { FormProvider, useForm } from 'react-hook-form';
import { createSearchParams, useFetcher, Outlet } from 'react-router-dom';

export interface EngagementUpdateData {
id: number;
name: string;
start_date: Dayjs;
end_date: Dayjs;
status_id: number;
description: string;
rich_description: string;
banner_filename: string;
status_block: string[];
title: string;
icon_name: string;
metadata_value: string;
taxon_id: number;
send_report: boolean;
slug: string;
request_type: string;
}

export const AuthoringContext = () => {
const fetcher = useFetcher();
const locationArray = window.location.href.split('/');
const slug = locationArray[locationArray.length - 1];
Comment on lines +28 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if you realized, but you're extracting all this information just to reconstruct your current location - try useLocation() instead.

Suggested change
const locationArray = window.location.href.split('/');
const slug = locationArray[locationArray.length - 1];
const { pathname } = useLocation();

const engagementUpdateForm = useForm<EngagementUpdateData>({
defaultValues: {
id: 0,
name: '',
start_date: undefined,
end_date: undefined,
status_id: 0,
description: '',
rich_description: '',
banner_filename: '',
status_block: [],
title: '',
icon_name: '',
metadata_value: '',
taxon_id: 0,
send_report: false,
slug: '',
request_type: '',
},
mode: 'onSubmit',
reValidateMode: 'onChange',
});

const onSubmit = async (data: EngagementUpdateData) => {
fetcher.submit(
createSearchParams({
id: data.id.toString(),
name: data.name,
start_date: data.start_date.format('YYYY-MM-DD'),
end_date: data.end_date.format('YYYY-MM-DD'),
description: data.description,
rich_description: data.rich_description,
banner_filename: data.banner_filename,
status_block: data.status_block,
title: data.title,
icon_name: data.icon_name,
metadata_value: data.metadata_value,
taxon_id: data.taxon_id.toString(),
send_report: data.send_report ? 'true' : 'false',
slug: data.slug,
request_type: data.request_type,
}),
{
method: 'post',
action: `/engagements/${data.id}/authoring/${slug}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are equivalent:

Suggested change
action: `/engagements/${data.id}/authoring/${slug}`,
action: pathname,

},
);
};

return (
<FormProvider {...engagementUpdateForm}>
<Outlet context={{ onSubmit }} />
</FormProvider>
);
};
Loading
Loading