-
Notifications
You must be signed in to change notification settings - Fork 19
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
Changes from 7 commits
d0273d8
cb3439e
9b7a990
02911d0
68c61b0
c145283
e60b306
0702a23
d0cc4f7
66191a9
8569d78
33c3cf2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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} /> | ||
<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'; | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
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; | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
}; | ||||||||||
|
||||||||||
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> | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
<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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||||||||||
<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" /> | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move icon to
Suggested change
|
||||||||||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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}`, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are equivalent:
Suggested change
|
||||||||
}, | ||||||||
); | ||||||||
}; | ||||||||
|
||||||||
return ( | ||||||||
<FormProvider {...engagementUpdateForm}> | ||||||||
<Outlet context={{ onSubmit }} /> | ||||||||
</FormProvider> | ||||||||
); | ||||||||
}; |
There was a problem hiding this comment.
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