From 2624f68675cc6cdf0e9292e5489a7024e5e478a3 Mon Sep 17 00:00:00 2001 From: Carson Full Date: Wed, 27 Sep 2023 09:16:30 -0500 Subject: [PATCH 1/4] Refactor submit button logic out into hook --- src/components/form/SubmitButton.tsx | 135 +++++++++++++++------------ 1 file changed, 73 insertions(+), 62 deletions(-) diff --git a/src/components/form/SubmitButton.tsx b/src/components/form/SubmitButton.tsx index d635fca330..c80bd0ebc5 100644 --- a/src/components/form/SubmitButton.tsx +++ b/src/components/form/SubmitButton.tsx @@ -35,55 +35,7 @@ export const SubmitButton = forwardRef( { children, spinner = true, action, disabled, ...rest }, ref ) { - const form = useForm('SubmitButton'); - const { - hasValidationErrors, - submitting, - touched, - validating, - submitErrors, - dirtyFieldsSinceLastSubmit, - values, - } = useFormState({ - subscription: { - submitErrors: true, - dirtyFieldsSinceLastSubmit: true, - hasValidationErrors: true, - submitting: true, - touched: true, - validating: true, - values: true, - }, - }); - // Ignore FORM_ERROR since it doesn't count as it doesn't go to a field. - // We'll assume that the form _can_ be re-submitted with these errors. - // It could be a server error, connection error, etc. - const fieldSubmitErrors = useMemo( - () => omit(submitErrors ?? {}, FORM_ERROR), - [submitErrors] - ); - - const allFieldsTouched = touched - ? Object.values(touched).every((field) => field) - : false; - - const allFieldsWithSubmitErrorsAreDirty = useMemo(() => { - if (!submitErrors || Object.keys(fieldSubmitErrors).length === 0) { - return true; - } - const unchangedSubmitErrors = Object.keys( - dirtyFieldsSinceLastSubmit - ).reduce( - (remaining: Partial | undefined, field) => { - return remaining ? setIn(remaining, field, undefined) : undefined; - }, - cloneDeep(fieldSubmitErrors) - ); - return ( - !unchangedSubmitErrors || - Object.keys(unchangedSubmitErrors).length === 0 - ); - }, [submitErrors, fieldSubmitErrors, dirtyFieldsSinceLastSubmit]); + const sb = useSubmitButton({ action }); return ( ( return; } e.preventDefault(); - form.change('submitAction', action); - - void form.submit(); + sb.submit(); }} type="submit" - disabled={ - disabled || - submitting || - validating || - (allFieldsTouched && hasValidationErrors) || - // disable if there are submit/server errors for specific fields - // and they have not been changed since last submit - !allFieldsWithSubmitErrorsAreDirty - } - progress={spinner && submitting && values.submitAction === action} + disabled={disabled || sb.disabled} + progress={spinner && sb.submitting} ref={ref} > {Children.count(children) ? children : 'Submit'} @@ -120,3 +62,72 @@ export const SubmitButton = forwardRef( ); } ); + +export const useSubmitButton = ({ action }: { action?: string }) => { + const form = useForm('SubmitButton'); + const { + hasValidationErrors, + submitting, + touched, + validating, + submitErrors, + dirtyFieldsSinceLastSubmit, + values, + } = useFormState({ + subscription: { + submitErrors: true, + dirtyFieldsSinceLastSubmit: true, + hasValidationErrors: true, + submitting: true, + touched: true, + validating: true, + values: true, + }, + }); + // Ignore FORM_ERROR since it doesn't count as it doesn't go to a field. + // We'll assume that the form _can_ be re-submitted with these errors. + // It could be a server error, connection error, etc. + const fieldSubmitErrors = useMemo( + () => omit(submitErrors ?? {}, FORM_ERROR), + [submitErrors] + ); + + const allFieldsTouched = touched + ? Object.values(touched).every((field) => field) + : false; + + const allFieldsWithSubmitErrorsAreDirty = useMemo(() => { + if (!submitErrors || Object.keys(fieldSubmitErrors).length === 0) { + return true; + } + const unchangedSubmitErrors = Object.keys( + dirtyFieldsSinceLastSubmit + ).reduce( + (remaining: Partial | undefined, field) => { + return remaining ? setIn(remaining, field, undefined) : undefined; + }, + cloneDeep(fieldSubmitErrors) + ); + return ( + !unchangedSubmitErrors || Object.keys(unchangedSubmitErrors).length === 0 + ); + }, [submitErrors, fieldSubmitErrors, dirtyFieldsSinceLastSubmit]); + + const disabled = + submitting || + validating || + (allFieldsTouched && hasValidationErrors) || + // disable if there are submit/server errors for specific fields + // and they have not been changed since last submit + !allFieldsWithSubmitErrorsAreDirty; + + const progress = submitting && values.submitAction === action; + + const submit = () => { + form.change('submitAction', action); + + void form.submit(); + }; + + return { disabled, submitting: progress, submit }; +}; From 62334241a25e0689359c0e2797b28e9a6d154349 Mon Sep 17 00:00:00 2001 From: Carson Full Date: Wed, 27 Sep 2023 13:45:43 -0500 Subject: [PATCH 2/4] Refactor VariantAccordion out of PromptResponse steps --- .../VariantResponsesAccordion.tsx | 116 +++++++----------- .../EditForm/Steps/VariantAccordion.tsx | 39 ++++++ 2 files changed, 83 insertions(+), 72 deletions(-) create mode 100644 src/scenes/ProgressReports/EditForm/Steps/VariantAccordion.tsx diff --git a/src/scenes/ProgressReports/EditForm/Steps/PromptVariant/VariantResponsesAccordion.tsx b/src/scenes/ProgressReports/EditForm/Steps/PromptVariant/VariantResponsesAccordion.tsx index f1ae3f4128..ca08c67a24 100644 --- a/src/scenes/ProgressReports/EditForm/Steps/PromptVariant/VariantResponsesAccordion.tsx +++ b/src/scenes/ProgressReports/EditForm/Steps/PromptVariant/VariantResponsesAccordion.tsx @@ -1,16 +1,10 @@ -import { ExpandMore } from '@mui/icons-material'; -import { - Accordion, - AccordionDetails, - AccordionSummary, - Typography, -} from '@mui/material'; +import { Typography } from '@mui/material'; import { ReactNode, useState } from 'react'; import { Scalars } from '~/api/schema/schema.graphql'; import { VariantResponseFragment as VariantResponse } from '~/common/fragments'; import { Form, FormProps, SavingStatus } from '~/components/form'; import { RichTextField, RichTextView } from '~/components/RichText'; -import { RoleIcon } from '~/components/RoleIcon'; +import { VariantAccordion } from '../VariantAccordion'; import { InstructionsToggle } from './VariantInstructionsToggle'; interface FormShape { @@ -27,11 +21,10 @@ export interface VariantResponsesAccordionProps export const VariantResponsesAccordion = ({ response, - expanded: _expanded, + expanded, instructions, onSubmit, }: VariantResponsesAccordionProps) => { - const [expanded, setExpanded] = useState(_expanded ?? false); const [initialValues] = useState(() => ({ variant: response.variant.key, response: response.response.value, @@ -42,68 +35,47 @@ export const VariantResponsesAccordion = ({ } return ( - - } - sx={{ - '& .MuiAccordionSummary-content': { - alignItems: 'center', - }, - }} - onClick={() => setExpanded(!expanded)} - > - - {response.variant.label} - + + {response.response.canEdit ? ( + + onSubmit={onSubmit} + initialValues={initialValues} + autoSubmit + > + {({ handleSubmit, submitting }) => ( +
+ {instructions && ( + + {instructions} + + )} - - {response.response.canEdit ? ( - - onSubmit={onSubmit} - initialValues={initialValues} - autoSubmit - > - {({ handleSubmit, submitting }) => ( - - {instructions && ( - - {instructions} - - )} - - - } - /> - - )} - - ) : response.response.value ? ( - - ) : ( - - No response given yet - - )} -
-
+ + } + /> + + )} + + ) : response.response.value ? ( + + ) : ( + + No response given yet + + )} + ); }; diff --git a/src/scenes/ProgressReports/EditForm/Steps/VariantAccordion.tsx b/src/scenes/ProgressReports/EditForm/Steps/VariantAccordion.tsx new file mode 100644 index 0000000000..e5bdedc23b --- /dev/null +++ b/src/scenes/ProgressReports/EditForm/Steps/VariantAccordion.tsx @@ -0,0 +1,39 @@ +import { ExpandMore } from '@mui/icons-material'; +import { Accordion, AccordionDetails, AccordionSummary } from '@mui/material'; +import { useToggle } from 'ahooks'; +import { ChildrenProp } from '~/common'; +import { VariantFragment } from '~/common/fragments'; +import { RoleIcon } from '~/components/RoleIcon'; + +export interface VariantAccordionProps extends ChildrenProp { + variant: VariantFragment; + expanded?: boolean; +} + +export const VariantAccordion = ({ + variant, + expanded: expandedInput, + children, +}: VariantAccordionProps) => { + const [expanded, { toggle }] = useToggle(expandedInput ?? false); + + return ( + + } + sx={{ + '& .MuiAccordionSummary-content': { + alignItems: 'center', + }, + }} + onClick={toggle} + > + + {variant.label} + + + {children} + + ); +}; From 4074c5fb86063faa8e82dce294bde39aba717c8d Mon Sep 17 00:00:00 2001 From: Brent Date: Wed, 27 Sep 2023 13:50:51 -0500 Subject: [PATCH 3/4] Implement progress report media step Co-authored-by: Carson Full --- src/components/form/Dropzone.tsx | 29 +-- src/components/form/SelectField.tsx | 4 +- .../EditForm/ProgressReportEdit.graphql | 1 + .../EditForm/Steps/Media/ImageField.tsx | 206 ++++++++++++++++++ .../EditForm/Steps/Media/MediaInfoForm.tsx | 131 +++++++++++ .../EditForm/Steps/Media/MediaStep.graphql | 32 +++ .../EditForm/Steps/Media/MediaStep.tsx | 116 ++++++++++ .../EditForm/Steps/Media/index.tsx | 1 + .../Steps/Media/progressReportMedia.graphql | 25 +++ .../ProgressReports/EditForm/Steps/index.ts | 2 + 10 files changed, 532 insertions(+), 15 deletions(-) create mode 100644 src/scenes/ProgressReports/EditForm/Steps/Media/ImageField.tsx create mode 100644 src/scenes/ProgressReports/EditForm/Steps/Media/MediaInfoForm.tsx create mode 100644 src/scenes/ProgressReports/EditForm/Steps/Media/MediaStep.graphql create mode 100644 src/scenes/ProgressReports/EditForm/Steps/Media/MediaStep.tsx create mode 100644 src/scenes/ProgressReports/EditForm/Steps/Media/index.tsx create mode 100644 src/scenes/ProgressReports/EditForm/Steps/Media/progressReportMedia.graphql diff --git a/src/components/form/Dropzone.tsx b/src/components/form/Dropzone.tsx index 8f85c6977e..3f93dfb41c 100644 --- a/src/components/form/Dropzone.tsx +++ b/src/components/form/Dropzone.tsx @@ -1,5 +1,6 @@ import { Clear as ClearIcon } from '@mui/icons-material'; import { + Box, IconButton, List, ListItem, @@ -12,20 +13,17 @@ import { ReactNode } from 'react'; import { useDropzone } from 'react-dropzone'; import { makeStyles } from 'tss-react/mui'; import { Except } from 'type-fest'; +import { extendSx, StyleProps } from '~/common'; import { fileIcon } from '../files/fileTypes'; import { FieldConfig, useField } from './useField'; -const useStyles = makeStyles()(({ palette, spacing }) => { +const useStyles = makeStyles()(({ palette, spacing, shape }) => { const dropzoneHoverStyle = { - backgroundColor: palette.grey[200], borderColor: palette.primary.main, }; return { - root: { - marginBottom: spacing(2), - }, dropzone: { - backgroundColor: palette.grey[300], + borderRadius: shape.borderRadius, border: `2px dashed ${palette.divider}`, cursor: 'pointer', padding: spacing(3), @@ -42,17 +40,20 @@ const useStyles = makeStyles()(({ palette, spacing }) => { }; }); -export type DropzoneFieldProps = Except, 'multiple'> & { - label?: ReactNode; - multiple?: boolean; - className?: string; -}; +export type DropzoneFieldProps = Except, 'multiple'> & + StyleProps & { + label?: ReactNode; + multiple?: boolean; + disableFileList?: boolean; + }; export function DropzoneField({ multiple = false, label = 'Click or drag files here', name: nameProp, className, + sx, + disableFileList, }: DropzoneFieldProps) { const { classes, cx } = useStyles(); @@ -91,7 +92,7 @@ export function DropzoneField({ }); return ( -
+
- {currentFiles.length > 0 && ( + {!disableFileList && currentFiles.length > 0 && ( {currentFiles.map((file, index) => { const { name, type } = file; @@ -129,6 +130,6 @@ export function DropzoneField({ })} )} -
+ ); } diff --git a/src/components/form/SelectField.tsx b/src/components/form/SelectField.tsx index 542da7b168..987f95844b 100644 --- a/src/components/form/SelectField.tsx +++ b/src/components/form/SelectField.tsx @@ -96,7 +96,9 @@ export function SelectField({ ))} - {getHelperText(meta, helperText)} + {helperText !== false && ( + {getHelperText(meta, helperText)} + )} ); } diff --git a/src/scenes/ProgressReports/EditForm/ProgressReportEdit.graphql b/src/scenes/ProgressReports/EditForm/ProgressReportEdit.graphql index 728a93e91d..364e8ecab7 100644 --- a/src/scenes/ProgressReports/EditForm/ProgressReportEdit.graphql +++ b/src/scenes/ProgressReports/EditForm/ProgressReportEdit.graphql @@ -22,6 +22,7 @@ fragment ProgressReportEdit on ProgressReport { progressForAllVariants { ...ProgressReportProgress } + ...mediaStep status { ...ProgressReportStatus } diff --git a/src/scenes/ProgressReports/EditForm/Steps/Media/ImageField.tsx b/src/scenes/ProgressReports/EditForm/Steps/Media/ImageField.tsx new file mode 100644 index 0000000000..e58ef73484 --- /dev/null +++ b/src/scenes/ProgressReports/EditForm/Steps/Media/ImageField.tsx @@ -0,0 +1,206 @@ +import { + AddPhotoAlternate as AddPhotoIcon, + Delete as DeleteIcon, + Download as DownloadIcon, + MoreVert as TripleDotIcon, +} from '@mui/icons-material'; +import { + Box, + Fab, + FabProps, + ListItemIcon, + Menu, + MenuItem, +} from '@mui/material'; +import { useId, useState } from 'react'; +import { Sensitivity } from '~/api/schema.graphql'; +import { extendSx, square, StyleProps } from '~/common'; +import { DropzoneField, useSubmitButton } from '~/components/form'; +import { SensitivityIcon } from '../../../../../components/Sensitivity'; +import { ImageFragment } from './progressReportMedia.graphql'; + +export interface ImageFieldProps extends StyleProps { + name: string; + disabled?: boolean; + sensitivity?: Sensitivity; + current?: ImageFragment; + canDelete: boolean; + instructionMessage?: string; +} + +export const ImageField = ({ + name, + disabled, + sensitivity, + current, + canDelete, + instructionMessage, + ...props +}: ImageFieldProps) => { + return !current ? ( + *': { + flex: 1, + + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + position: 'relative', + }, + ...extendSx(props.sx), + ]} + label={ + <> + +
{instructionMessage || 'Click or drop to add photo'}
+ {sensitivity && ( + + )} + + } + /> + ) : ( + + + {sensitivity && ( + ({ + position: 'absolute', + top: 0, + left: 0, + bgcolor: 'background.paper', + borderEndEndRadius: theme.shape.borderRadius, + })} + /> + )} + ({ + position: 'absolute', + top: theme.spacing(1), + right: theme.spacing(1), + bgcolor: theme.palette.background.paper, + })} + > + { + if (e.key === 'Enter') { + e.currentTarget.click(); + } + }} + > + + + + Download + + {canDelete && } + + + ); +}; + +const DeleteImageMenuItem = () => { + const sb = useSubmitButton({ action: 'delete' }); + return ( + + + + + Delete + + ); +}; + +type MoreActionsButtonProps = FabProps; + +export const MoreActionsButton = ({ + children, + ...props +}: MoreActionsButtonProps) => { + const id = useId(); + const [anchorEl, setAnchorEl] = useState(null); + const handleClose = () => setAnchorEl(null); + + return ( + <> + setAnchorEl(e.currentTarget)} + > + + + { + if (e.key === 'Enter') { + setTimeout(() => handleClose(), 0); + return; + } + }} + > + {children} + + + ); +}; diff --git a/src/scenes/ProgressReports/EditForm/Steps/Media/MediaInfoForm.tsx b/src/scenes/ProgressReports/EditForm/Steps/Media/MediaInfoForm.tsx new file mode 100644 index 0000000000..cb30bea2e0 --- /dev/null +++ b/src/scenes/ProgressReports/EditForm/Steps/Media/MediaInfoForm.tsx @@ -0,0 +1,131 @@ +import { Box, Stack } from '@mui/material'; +import { useMemo } from 'react'; +import { + ProgressReportMediaCategory as Category, + Sensitivity, +} from '~/api/schema.graphql'; +import { + ProgressReportMediaCategoryLabels, + ProgressReportMediaCategoryList, +} from '~/api/schema/enumLists'; +import { labelFrom } from '~/common'; +import { VariantFragment } from '~/common/fragments'; +import { + Form, + FormProps, + SelectField, + SubmitAction, + TextField, +} from '~/components/form'; +import { ImageField } from './ImageField'; +import { ProgressReportMediaFragment } from './progressReportMedia.graphql'; + +interface MediaFormState extends SubmitAction<'delete'> { + variant: VariantFragment; + id?: string; + newFile?: File[]; + newVersion?: File[]; + category?: Category | null; + caption?: string | null; +} + +export type MediaInfoFormProps = Omit< + FormProps, + 'initialValues' +> & { + sensitivity: Sensitivity; + variant: VariantFragment; + existingMedia?: ProgressReportMediaFragment; + isFirstUpload: boolean; +}; + +export const MediaInfoForm = ({ + existingMedia, + sensitivity, + variant, + isFirstUpload, + ...props +}: MediaInfoFormProps) => { + const existingImage = + existingMedia?.media.__typename === 'Image' + ? existingMedia.media + : undefined; + + const initialValues = useMemo( + () => ({ + variant, + id: existingMedia?.id, + category: existingMedia?.category, + caption: existingImage?.caption, + variantGroup: existingMedia?.variantGroup, + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [variant, existingMedia] + ); + + return ( + + autoSubmit + {...props} + initialValues={initialValues} + sendIfClean="delete" + > + {({ handleSubmit }) => ( + + {!existingImage && !isFirstUpload && ( + + )} + + {existingImage && ( + + + + + )} + + )} + + ); +}; diff --git a/src/scenes/ProgressReports/EditForm/Steps/Media/MediaStep.graphql b/src/scenes/ProgressReports/EditForm/Steps/Media/MediaStep.graphql new file mode 100644 index 0000000000..c419e84319 --- /dev/null +++ b/src/scenes/ProgressReports/EditForm/Steps/Media/MediaStep.graphql @@ -0,0 +1,32 @@ +mutation CreateMedia($input: UploadProgressReportMedia!) { + uploadProgressReportMedia(input: $input) { + ...mediaStep + } +} + +mutation UpdateMedia($input: UpdateProgressReportMedia!) { + updateProgressReportMedia(input: $input) { + ...progressReportMedia + } +} +mutation DeleteMedia($deleteProgressReportMediaId: ID!) { + deleteProgressReportMedia(id: $deleteProgressReportMediaId) { + ...mediaStep + } +} + +fragment mediaStep on ProgressReport { + id + sensitivity + media(input: { sort: "createdAt", order: ASC }) { + items { + ...progressReportMedia + } + availableVariants { + variant { + ...variant + } + canCreate + } + } +} diff --git a/src/scenes/ProgressReports/EditForm/Steps/Media/MediaStep.tsx b/src/scenes/ProgressReports/EditForm/Steps/Media/MediaStep.tsx new file mode 100644 index 0000000000..93dc3e6ac6 --- /dev/null +++ b/src/scenes/ProgressReports/EditForm/Steps/Media/MediaStep.tsx @@ -0,0 +1,116 @@ +import { useMutation } from '@apollo/client'; +import { Box, Typography } from '@mui/material'; +import { useMemo } from 'react'; +import { useUploadFileAsync } from '~/components/files/hooks'; +import { UploadProgressReportMedia } from '../../../../../api/schema/schema.graphql'; +import { StepComponent } from '../step.types'; +import { VariantAccordion } from '../VariantAccordion'; +import { MediaInfoForm, MediaInfoFormProps } from './MediaInfoForm'; +import { + CreateMediaDocument, + DeleteMediaDocument, + UpdateMediaDocument, +} from './MediaStep.graphql'; + +export const MediaStep: StepComponent = ({ report }) => { + const { mediaItems, latestVariantGroupId } = useMemo(() => { + const mediaItems = report.media.availableVariants + .slice() + .reverse() + .flatMap(({ variant, canCreate }) => { + const existing = report.media.items.find( + (media) => media.variant.key === variant.key + ); + return existing || canCreate ? { variant, existing } : []; + }); + + const latestVariantGroupId = mediaItems.find( + (v) => v.existing?.variantGroup + )?.existing?.variantGroup; + + return { mediaItems, latestVariantGroupId }; + }, [report]); + + const [createMedia] = useMutation(CreateMediaDocument); + const [updateMedia] = useMutation(UpdateMediaDocument); + const [deleteMedia] = useMutation(DeleteMediaDocument); + const uploadFile = useUploadFileAsync(); + + const handleSubmit: MediaInfoFormProps['onSubmit'] = async (values) => { + if (values.submitAction === 'delete') { + if (!values.id) { + return; + } + await deleteMedia({ + variables: { + deleteProgressReportMediaId: values.id, + }, + }); + return; + } + + const newFileForNewGroup = + values.newFile && values.newFile.length > 0 + ? values.newFile[0] + : undefined; + const newFileForExistingGroup = + values.newVersion && values.newVersion.length > 0 + ? values.newVersion[0] + : undefined; + const newFile = newFileForNewGroup || newFileForExistingGroup; + + if (!values.id) { + if (!newFile) { + return; + } + const [uploadedImageInfo, finalizeUpload] = await uploadFile(newFile); + const input: UploadProgressReportMedia = { + reportId: report.id, + file: uploadedImageInfo, + variant: values.variant.key, + variantGroup: newFileForExistingGroup + ? latestVariantGroupId + : undefined, + }; + await createMedia({ + variables: { input }, + }).then(...finalizeUpload.tap); + return; + } + + if (values.id) { + await updateMedia({ + variables: { + input: { + id: values.id, + category: values.category, + caption: values.caption, + }, + }, + }); + return; + } + }; + + return ( + + + Upload an image to go with your Report + + {mediaItems.map(({ variant, existing }) => ( + + + + ))} + + ); +}; + +MediaStep.enableWhen = ({ report }) => + report.media.availableVariants.length > 0; diff --git a/src/scenes/ProgressReports/EditForm/Steps/Media/index.tsx b/src/scenes/ProgressReports/EditForm/Steps/Media/index.tsx new file mode 100644 index 0000000000..45d84c58fd --- /dev/null +++ b/src/scenes/ProgressReports/EditForm/Steps/Media/index.tsx @@ -0,0 +1 @@ +export * from './MediaStep'; diff --git a/src/scenes/ProgressReports/EditForm/Steps/Media/progressReportMedia.graphql b/src/scenes/ProgressReports/EditForm/Steps/Media/progressReportMedia.graphql new file mode 100644 index 0000000000..b0832a7163 --- /dev/null +++ b/src/scenes/ProgressReports/EditForm/Steps/Media/progressReportMedia.graphql @@ -0,0 +1,25 @@ +fragment progressReportMedia on ProgressReportMedia { + variant { + ...variant + } + variantGroup + id + category + media { + id + ...image + } + canEdit + canDelete +} + +fragment image on Image { + id + caption + url + dimensions { + aspectRatio + height + width + } +} diff --git a/src/scenes/ProgressReports/EditForm/Steps/index.ts b/src/scenes/ProgressReports/EditForm/Steps/index.ts index c103dd9e07..c13e84000a 100644 --- a/src/scenes/ProgressReports/EditForm/Steps/index.ts +++ b/src/scenes/ProgressReports/EditForm/Steps/index.ts @@ -1,5 +1,6 @@ import { CommunityStoryStep } from './CommunityStory'; import { ExplanationOfProgress } from './ExplanationOfProgress/ExplanationOfProgress'; +import { MediaStep } from './Media'; import { ProgressStep } from './ProgressStep'; import { GroupedStepMapShape } from './step.types'; import { SubmitReportStep } from './SubmitReportStep'; @@ -9,6 +10,7 @@ export const Steps: GroupedStepMapShape = { 'Investor Connection': [ ['Team News', TeamNewsStep], ['Story', CommunityStoryStep], + ['Media', MediaStep], ], 'Project Management': [ ['Progress', ProgressStep], From a5aa20aedbc3fafd64dd425c5f44de4be43d4748 Mon Sep 17 00:00:00 2001 From: Carson Full Date: Wed, 27 Sep 2023 09:39:23 -0500 Subject: [PATCH 4/4] Fix/hack merging lists that are sorted by variant --- .../typePolicies/lists/page-limit-pagination.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/api/schema/typePolicies/lists/page-limit-pagination.ts b/src/api/schema/typePolicies/lists/page-limit-pagination.ts index 1a974d7f4c..6b0991da76 100644 --- a/src/api/schema/typePolicies/lists/page-limit-pagination.ts +++ b/src/api/schema/typePolicies/lists/page-limit-pagination.ts @@ -14,6 +14,7 @@ import { ValueIteratee, } from 'lodash'; import { isListNotEmpty, Nullable, splice, unwrapSecured } from '~/common'; +import { VariantFragment } from '../../../../common/fragments'; import { InputArg, PaginatedListInput, @@ -126,6 +127,22 @@ const mergeList = ( const readSecuredField = (field: string) => (ref: Reference) => { const secured = readField(field, ref); const fieldVal = unwrapSecured(secured); + + // Hack to handle sorting with variants, + // whose sort keys do not match a field name that's a primitive. + // This logic needs to be exposed to pageLimitPagination + // Something like pageLimitPagination({ + // getSortValue: { + // variant: (variant) => variant.key, + if ( + fieldVal && + typeof fieldVal === 'object' && + '__typename' in fieldVal && + fieldVal.__typename === 'Variant' + ) { + return (fieldVal as VariantFragment).key; + } + return fieldVal; };