diff --git a/src/attributes/components/AttributeDetails/AttributeDetails.tsx b/src/attributes/components/AttributeDetails/AttributeDetails.tsx index 1c56d005d..edfd58577 100644 --- a/src/attributes/components/AttributeDetails/AttributeDetails.tsx +++ b/src/attributes/components/AttributeDetails/AttributeDetails.tsx @@ -197,7 +197,7 @@ const AttributeDetails: React.FC = props => { label={intl.formatMessage(messages.entityType)} name="entityType" onChange={onChange} - value={data.entityType} + value={data.entityType ?? undefined} /> )} diff --git a/src/attributes/components/AttributeDetails/NumericUnits.tsx b/src/attributes/components/AttributeDetails/NumericUnits.tsx index 37eba81b6..87e55acff 100644 --- a/src/attributes/components/AttributeDetails/NumericUnits.tsx +++ b/src/attributes/components/AttributeDetails/NumericUnits.tsx @@ -40,7 +40,7 @@ const useStyles = makeStyles( ); interface UnitData { - unit?: MeasurementUnitsEnum; + unit: MeasurementUnitsEnum | null | undefined; system?: UnitSystem; type?: UnitType; } @@ -145,7 +145,10 @@ export const NumericUnits: React.FC = ({ label={formatMessage(M.messages.unitSystem)} choices={systemChoices} onChange={({ target }: React.ChangeEvent) => - setUnitData({ system: target.value as UnitSystem }) + setUnitData(data => ({ + ...data, + system: target.value as UnitSystem, + })) } value={system} disabled={disabled} @@ -156,8 +159,8 @@ export const NumericUnits: React.FC = ({ label={formatMessage(M.messages.unitOf)} choices={typeChoices} onChange={({ target }: React.ChangeEvent) => - setUnitData(({ system }) => ({ - system, + setUnitData(data => ({ + ...data, type: target.value as UnitType, })) } @@ -168,7 +171,7 @@ export const NumericUnits: React.FC = ({ {...(type && !unit && errorProps)} testId="unit" label={formatMessage(M.messages.unit)} - choices={type ? unitChoices[system][type] : []} + choices={type && system ? unitChoices[system][type] : []} onChange={({ target }: React.ChangeEvent) => setUnitData(data => ({ ...data, @@ -177,7 +180,7 @@ export const NumericUnits: React.FC = ({ } disabled={!type || disabled} value={ - type && unitMapping[system][type].includes(unit) + type && system && unit && unitMapping[system][type].includes(unit) ? unit : undefined } diff --git a/src/attributes/components/AttributeList/AttributeList.tsx b/src/attributes/components/AttributeList/AttributeList.tsx index f7e871f27..3edbd5dc0 100644 --- a/src/attributes/components/AttributeList/AttributeList.tsx +++ b/src/attributes/components/AttributeList/AttributeList.tsx @@ -11,7 +11,7 @@ import { TablePaginationWithContext } from "@dashboard/components/TablePaginatio import TableRowLink from "@dashboard/components/TableRowLink"; import { AttributeFragment } from "@dashboard/graphql"; import { translateBoolean } from "@dashboard/intl"; -import { maybe, renderCollection } from "@dashboard/misc"; +import { renderCollection } from "@dashboard/misc"; import { ListActions, ListProps, SortPage } from "@dashboard/types"; import { getArrowDirection } from "@dashboard/utils/sort"; import { TableBody, TableCell, TableFooter } from "@material-ui/core"; @@ -95,7 +95,7 @@ const AttributeList: React.FC = ({ className={classes.colSlug} direction={ sort.sort === AttributeListUrlSortField.slug - ? getArrowDirection(sort.asc) + ? getArrowDirection(!!sort.asc) : undefined } arrowPosition="right" @@ -107,7 +107,7 @@ const AttributeList: React.FC = ({ className={classes.colName} direction={ sort.sort === AttributeListUrlSortField.name - ? getArrowDirection(sort.asc) + ? getArrowDirection(!!sort.asc) : undefined } onClick={() => onSort(AttributeListUrlSortField.name)} @@ -122,7 +122,7 @@ const AttributeList: React.FC = ({ className={classes.colVisible} direction={ sort.sort === AttributeListUrlSortField.visible - ? getArrowDirection(sort.asc) + ? getArrowDirection(!!sort.asc) : undefined } textAlign="center" @@ -138,7 +138,7 @@ const AttributeList: React.FC = ({ className={classes.colSearchable} direction={ sort.sort === AttributeListUrlSortField.searchable - ? getArrowDirection(sort.asc) + ? getArrowDirection(!!sort.asc) : undefined } textAlign="center" @@ -154,7 +154,7 @@ const AttributeList: React.FC = ({ className={classes.colFaceted} direction={ sort.sort === AttributeListUrlSortField.useInFacetedSearch - ? getArrowDirection(sort.asc) + ? getArrowDirection(!!sort.asc) : undefined } textAlign="center" @@ -185,14 +185,14 @@ const AttributeList: React.FC = ({ key={attribute ? attribute.id : "skeleton"} href={attribute && attributeUrl(attribute.id)} className={classes.link} - data-test-id={"id-" + maybe(() => attribute.id)} + data-test-id={`id-${attribute?.id}`} > toggle(attribute.id)} + onChange={() => toggle(attribute?.id ?? "")} /> @@ -204,7 +204,7 @@ const AttributeList: React.FC = ({ attribute.visibleInStorefront)} + data-test-visible={attribute?.visibleInStorefront} > {attribute ? ( translateBoolean(attribute.visibleInStorefront, intl) @@ -215,9 +215,7 @@ const AttributeList: React.FC = ({ attribute.filterableInDashboard, - )} + data-test-searchable={attribute?.filterableInDashboard} > {attribute ? ( translateBoolean(attribute.filterableInDashboard, intl) @@ -228,9 +226,9 @@ const AttributeList: React.FC = ({ attribute.filterableInStorefront, - )} + data-test-use-in-faceted-search={ + attribute?.filterableInStorefront + } > {attribute ? ( translateBoolean(attribute.filterableInStorefront, intl) diff --git a/src/attributes/components/AttributePage/AttributePage.tsx b/src/attributes/components/AttributePage/AttributePage.tsx index 00f9f0333..2fda93f39 100644 --- a/src/attributes/components/AttributePage/AttributePage.tsx +++ b/src/attributes/components/AttributePage/AttributePage.tsx @@ -22,7 +22,6 @@ import { import { SubmitPromise } from "@dashboard/hooks/useForm"; import useNavigator from "@dashboard/hooks/useNavigator"; import { sectionNames } from "@dashboard/intl"; -import { maybe } from "@dashboard/misc"; import { ListSettings, ReorderAction } from "@dashboard/types"; import { mapEdgesToItems, mapMetadataItemToInput } from "@dashboard/utils/maps"; import useMetadataChangeTrigger from "@dashboard/utils/metadata/useMetadataChangeTrigger"; @@ -37,11 +36,13 @@ import AttributeProperties from "../AttributeProperties"; import AttributeValues from "../AttributeValues"; export interface AttributePageProps { - attribute: AttributeDetailsFragment | null; + attribute?: AttributeDetailsFragment | null | undefined; disabled: boolean; errors: AttributeErrorFragment[]; saveButtonBarState: ConfirmButtonTransitionState; - values: AttributeDetailsQuery["attribute"]["choices"]; + values?: + | NonNullable["choices"] + | undefined; onDelete: () => void; onSubmit: (data: AttributePageFormData) => SubmitPromise; onValueAdd: () => void; @@ -64,7 +65,7 @@ export interface AttributePageFormData extends MetadataFormData { availableInGrid: boolean; filterableInDashboard: boolean; inputType: AttributeInputTypeEnum; - entityType: AttributeEntityTypeEnum; + entityType: AttributeEntityTypeEnum | null; filterableInStorefront: boolean; name: string; slug: string; @@ -102,59 +103,51 @@ const AttributePage: React.FC = ({ makeChangeHandler: makeMetadataChangeHandler, } = useMetadataChangeTrigger(); - const initialForm: AttributePageFormData = - attribute === null - ? { - availableInGrid: true, - entityType: null, - filterableInDashboard: true, - filterableInStorefront: true, - inputType: AttributeInputTypeEnum.DROPDOWN, - metadata: [], - name: "", - privateMetadata: [], - slug: "", - storefrontSearchPosition: "", - type: AttributeTypeEnum.PRODUCT_TYPE, - valueRequired: true, - visibleInStorefront: true, - unit: undefined, - } - : { - availableInGrid: attribute?.availableInGrid ?? true, - entityType: attribute?.entityType ?? null, - filterableInDashboard: attribute?.filterableInDashboard ?? true, - filterableInStorefront: attribute?.filterableInStorefront ?? true, - inputType: attribute?.inputType ?? AttributeInputTypeEnum.DROPDOWN, - metadata: attribute?.metadata?.map(mapMetadataItemToInput), - name: attribute?.name ?? "", - privateMetadata: attribute?.privateMetadata?.map( - mapMetadataItemToInput, - ), - slug: attribute?.slug ?? "", - storefrontSearchPosition: - attribute?.storefrontSearchPosition.toString() ?? "", - type: attribute?.type || AttributeTypeEnum.PRODUCT_TYPE, - valueRequired: !!attribute?.valueRequired ?? true, - visibleInStorefront: attribute?.visibleInStorefront ?? true, - unit: attribute?.unit || null, - }; + const initialForm: AttributePageFormData = !attribute + ? { + availableInGrid: true, + entityType: null, + filterableInDashboard: true, + filterableInStorefront: true, + inputType: AttributeInputTypeEnum.DROPDOWN, + metadata: [], + name: "", + privateMetadata: [], + slug: "", + storefrontSearchPosition: "", + type: AttributeTypeEnum.PRODUCT_TYPE, + valueRequired: true, + visibleInStorefront: true, + unit: undefined, + } + : { + availableInGrid: attribute.availableInGrid, + entityType: attribute.entityType, + filterableInDashboard: attribute.filterableInDashboard, + filterableInStorefront: attribute.filterableInStorefront, + inputType: attribute?.inputType ?? AttributeInputTypeEnum.DROPDOWN, + metadata: attribute.metadata.map(mapMetadataItemToInput), + name: attribute?.name ?? "", + privateMetadata: attribute.privateMetadata.map(mapMetadataItemToInput), + slug: attribute?.slug ?? "", + storefrontSearchPosition: attribute.storefrontSearchPosition.toString(), + type: attribute?.type ?? AttributeTypeEnum.PRODUCT_TYPE, + valueRequired: !!attribute.valueRequired, + visibleInStorefront: attribute.visibleInStorefront, + unit: attribute?.unit ?? null, + }; const handleSubmit = (data: AttributePageFormData) => { - const metadata = - !attribute || isMetadataModified ? data.metadata : undefined; + const metadata = !attribute || isMetadataModified ? data.metadata : []; const privateMetadata = - !attribute || isPrivateMetadataModified - ? data.privateMetadata - : undefined; - const type = attribute === null ? data.type : undefined; + !attribute || isPrivateMetadataModified ? data.privateMetadata : []; return onSubmit({ ...data, metadata, privateMetadata, slug: data.slug || slugify(data.name).toLowerCase(), - type, + type: data.type, }); }; @@ -185,13 +178,13 @@ const AttributePage: React.FC = ({ attribute.name) + : attribute.name } /> @@ -215,7 +208,7 @@ const AttributePage: React.FC = ({ = ({ navigate(attributeListUrl())} onSubmit={submit} diff --git a/src/attributes/components/AttributeSwatchField/AttributeSwatchField.tsx b/src/attributes/components/AttributeSwatchField/AttributeSwatchField.tsx index a8780fb98..b6cce23e8 100644 --- a/src/attributes/components/AttributeSwatchField/AttributeSwatchField.tsx +++ b/src/attributes/components/AttributeSwatchField/AttributeSwatchField.tsx @@ -41,11 +41,9 @@ const AttributeSwatchField: React.FC { setProcessing(true); - const { - data: { fileUpload }, - } = await uploadFile({ variables: { file } }); + const { data } = await uploadFile({ variables: { file } }); - if (fileUpload.errors?.length) { + if (data?.fileUpload?.errors?.length) { notify({ status: "error", title: intl.formatMessage(errorMessages.imgageUploadErrorTitle), @@ -53,8 +51,8 @@ const AttributeSwatchField: React.FC { +const getSwatchCellStyle = (value?: AttributeValueFragment | undefined) => { if (!value) { return; } return value.file ? { backgroundImage: `url(${value.file.url})` } - : { backgroundColor: value.value }; + : { backgroundColor: value.value ?? undefined }; }; const AttributeValues: React.FC = ({ @@ -198,7 +198,9 @@ const AttributeValues: React.FC = ({ onValueDelete(value.id))} + onClick={stopPropagation(() => + onValueDelete(value?.id ?? ""), + )} > diff --git a/src/attributes/errors.ts b/src/attributes/errors.ts index e58131b83..0ea44df5f 100644 --- a/src/attributes/errors.ts +++ b/src/attributes/errors.ts @@ -14,9 +14,9 @@ const messages = defineMessages({ }); export function getAttributeSlugErrorMessage( - err: AttributeErrorFragment, + err: AttributeErrorFragment | undefined, intl: IntlShape, -): string { +): string | undefined { switch (err?.code) { case AttributeErrorCode.UNIQUE: return intl.formatMessage(messages.attributeSlugUnique); @@ -26,9 +26,9 @@ export function getAttributeSlugErrorMessage( } export function getAttributeValueErrorMessage( - err: AttributeErrorFragment, + err: AttributeErrorFragment | undefined, intl: IntlShape, -): string { +): string | undefined { switch (err?.code) { case AttributeErrorCode.ALREADY_EXISTS: return intl.formatMessage(messages.attributeValueAlreadyExists); diff --git a/src/attributes/fixtures.ts b/src/attributes/fixtures.ts index 4f661a12a..e4c55d25b 100644 --- a/src/attributes/fixtures.ts +++ b/src/attributes/fixtures.ts @@ -80,8 +80,10 @@ export const attribute: AttributeDetailsQuery["attribute"] = { visibleInStorefront: true, }; -export const attributes: Array = [ +export const attributes: Array["edges"][0]["node"] & + NonNullable["attributes"][0]["attribute"]> = [ { __typename: "Attribute" as "Attribute", entityType: AttributeEntityTypeEnum.PRODUCT, diff --git a/src/attributes/utils/data.ts b/src/attributes/utils/data.ts index f4c5897c1..f9a276d0e 100644 --- a/src/attributes/utils/data.ts +++ b/src/attributes/utils/data.ts @@ -66,7 +66,7 @@ export function filterable( attribute: Pick, ): boolean { return ATTRIBUTE_TYPES_WITH_CONFIGURABLE_FACED_NAVIGATION.includes( - attribute.inputType, + attribute.inputType!, ); } @@ -86,9 +86,9 @@ export function attributeValueFragmentToFormData( data: AttributeValueFragment | null, ): AttributeValueEditDialogFormData { return { - name: data?.name, - value: data?.value, - contentType: data?.file?.contentType, + name: data?.name ?? "", + value: data?.value ?? "", + contentType: data?.file?.contentType ?? "", fileUrl: data?.file?.url, }; } @@ -265,7 +265,7 @@ export const mergeAttributeValues = ( ) => { const attribute = attributes.find(attribute => attribute.id === attributeId); - return attribute.value + return attribute?.value ? [...attribute.value, ...attributeValues] : attributeValues; }; @@ -332,8 +332,8 @@ export const getAttributesOfUploadedFiles = ( const attribute = fileValuesToUpload[index]; return { - file: uploadFileResult.data.fileUpload.uploadedFile.url, - contentType: uploadFileResult.data.fileUpload.uploadedFile.contentType, + file: uploadFileResult.data?.fileUpload?.uploadedFile?.url, + contentType: uploadFileResult.data?.fileUpload?.uploadedFile?.contentType, id: attribute.id, values: [], }; @@ -380,7 +380,7 @@ export const getFileAttributeDisplayData = ( export const getPageReferenceAttributeDisplayData = ( attribute: AttributeInput, - referencePages: RelayToFlat, + referencePages: RelayToFlat>, ) => ({ ...attribute, data: { @@ -388,12 +388,18 @@ export const getPageReferenceAttributeDisplayData = ( references: referencePages?.length > 0 && attribute.value?.length > 0 ? mapPagesToChoices( - attribute.value.map(value => { + attribute.value.reduce< + RelayToFlat> + >((acc, value) => { const reference = referencePages.find( reference => reference.id === value, ); - return { ...reference }; - }), + + if (reference) { + acc.push(reference); + } + return acc; + }, []), ) : [], }, @@ -401,7 +407,7 @@ export const getPageReferenceAttributeDisplayData = ( export const getProductReferenceAttributeDisplayData = ( attribute: AttributeInput, - referenceProducts: RelayToFlat, + referenceProducts: RelayToFlat>, ) => ({ ...attribute, data: { @@ -409,12 +415,18 @@ export const getProductReferenceAttributeDisplayData = ( references: referenceProducts?.length > 0 && attribute.value?.length > 0 ? mapNodeToChoice( - attribute.value.map(value => { + attribute.value.reduce< + RelayToFlat> + >((acc, value) => { const reference = referenceProducts.find( reference => reference.id === value, ); - return { ...reference }; - }), + + if (reference) { + acc.push(reference); + } + return acc; + }, []), ) : [], }, @@ -422,7 +434,7 @@ export const getProductReferenceAttributeDisplayData = ( export const getProductVariantReferenceAttributeDisplayData = ( attribute: AttributeInput, - referenceProducts: RelayToFlat, + referenceProducts: RelayToFlat>, ) => ({ ...attribute, data: { @@ -430,12 +442,19 @@ export const getProductVariantReferenceAttributeDisplayData = ( references: referenceProducts?.length > 0 && attribute.value?.length > 0 ? mapNodeToChoice( - attribute.value.map(value => { - const reference = mapReferenceProductsToVariants( - referenceProducts, - ).find(reference => reference.id === value); - return { ...reference }; - }), + attribute.value.reduce>>( + (acc, value) => { + const reference = mapReferenceProductsToVariants( + referenceProducts, + ).find(reference => reference.id === value); + + if (reference) { + acc.push(reference); + } + return acc; + }, + [], + ), ) : [], }, @@ -443,8 +462,8 @@ export const getProductVariantReferenceAttributeDisplayData = ( export const getReferenceAttributeDisplayData = ( attribute: AttributeInput, - referencePages: RelayToFlat, - referenceProducts: RelayToFlat, + referencePages: RelayToFlat>, + referenceProducts: RelayToFlat>, ) => { if (attribute.data.entityType === AttributeEntityTypeEnum.PAGE) { return getPageReferenceAttributeDisplayData(attribute, referencePages); @@ -466,8 +485,8 @@ export const getReferenceAttributeDisplayData = ( export const getAttributesDisplayData = ( attributes: AttributeInput[], attributesWithNewFileValue: FormsetData, - referencePages: RelayToFlat, - referenceProducts: RelayToFlat, + referencePages: RelayToFlat>, + referenceProducts: RelayToFlat>, ) => attributes.map(attribute => { if (attribute.data.inputType === AttributeInputTypeEnum.REFERENCE) { @@ -499,11 +518,11 @@ export const getReferenceAttributeEntityTypeFromAttribute = ( attributes?.find(attribute => attribute.id === attributeId)?.data?.entityType; export const mapReferenceProductsToVariants = ( - referenceProducts: RelayToFlat, + referenceProducts: RelayToFlat>, ) => referenceProducts.flatMap(product => - product.variants.map(variant => ({ + (product.variants || []).map(variant => ({ ...variant, - name: product.name + " " + variant.name, + name: `${product.name} ${variant.name}`, })), ); diff --git a/src/attributes/utils/handlers.test.ts b/src/attributes/utils/handlers.test.ts index cc7dade14..6ed3fb84a 100644 --- a/src/attributes/utils/handlers.test.ts +++ b/src/attributes/utils/handlers.test.ts @@ -136,7 +136,7 @@ const createAttribute = ({ value, }: CreateAttribute): AttributeInput => ({ data: { - entityType: null, + entityType: undefined, inputType, isRequired: false, // those values don't matter @@ -146,7 +146,7 @@ const createAttribute = ({ }, id: ATTR_ID, label: "MyAttribute", - value: value !== null ? [value] : [], + value: value !== null && value !== undefined ? [value] : [], }); const createSelectAttribute = (value: string) => @@ -478,7 +478,7 @@ describe("Sending only changed attributes", () => { }); describe("works with file attributes", () => { it("removes existing image (img -> null)", () => { - const attribute = createFileAttribute(null); + const attribute = createFileAttribute(""); const prevAttribute = createNumericAttribute("bob.jpg"); const result = prepareAttributesInput({ @@ -494,7 +494,7 @@ describe("Sending only changed attributes", () => { }); it("adds new image (null -> img)", () => { const attribute = createFileAttribute("bob.jpg"); - const prevAttribute = createNumericAttribute(null); + const prevAttribute = createNumericAttribute(""); const uploadUrl = "http://some-url.com/media/file_upload/bob.jpg"; const result = prepareAttributesInput({ diff --git a/src/attributes/utils/handlers.ts b/src/attributes/utils/handlers.ts index d2625e1f3..493b8971c 100644 --- a/src/attributes/utils/handlers.ts +++ b/src/attributes/utils/handlers.ts @@ -48,7 +48,7 @@ export function createAttributeMultiChangeHandler( const newAttributeValues = toggle( value, - attribute.value, + attribute?.value ?? [], (a, b) => a === b, ); @@ -88,6 +88,7 @@ export function createFetchReferencesHandler( ) { fetchReferencePages(value); } else if ( + attribute.data?.entityType && [ AttributeEntityTypeEnum.PRODUCT, AttributeEntityTypeEnum.PRODUCT_VARIANT, @@ -116,6 +117,7 @@ export function createFetchMoreReferencesHandler( if (attribute.data.entityType === AttributeEntityTypeEnum.PAGE) { return fetchMoreReferencePages; } else if ( + attribute.data?.entityType && [ AttributeEntityTypeEnum.PRODUCT, AttributeEntityTypeEnum.PRODUCT_VARIANT, @@ -145,7 +147,7 @@ export function createAttributeFileChangeHandler( addAttributeNewFileValue({ data: null, id: attributeId, - label: null, + label: "", value, }); } @@ -167,8 +169,8 @@ export function createAttributeValueReorderHandler( ); const reorderedValues = move( - attribute.value[reorder.oldIndex], - attribute.value, + attribute?.value?.[reorder.oldIndex] ?? "", + attribute?.value ?? [], (a, b) => a === b, reorder.newIndex, ); @@ -194,7 +196,7 @@ function getFileInput( } return { file: attribute.data.selectedValues?.[0]?.file?.url, - contentType: attribute.data.selectedValues?.[0]?.file.contentType, + contentType: attribute.data.selectedValues?.[0]?.file?.contentType, id: attribute.id, }; } @@ -318,7 +320,9 @@ export const handleDeleteMultipleAttributeValues = async ( attributes: Array< | PageSelectedAttributeFragment | ProductFragment["attributes"][0] - | ProductVariantDetailsQuery["productVariant"]["nonSelectionAttributes"][0] + | NonNullable< + ProductVariantDetailsQuery["productVariant"] + >["nonSelectionAttributes"][0] >, deleteAttributeValue: ( variables: AttributeValueDeleteMutationVariables, diff --git a/src/attributes/views/AttributeCreate/AttributeCreate.tsx b/src/attributes/views/AttributeCreate/AttributeCreate.tsx index c11a00ce3..ee70e07d2 100644 --- a/src/attributes/views/AttributeCreate/AttributeCreate.tsx +++ b/src/attributes/views/AttributeCreate/AttributeCreate.tsx @@ -86,7 +86,7 @@ const AttributeDetails: React.FC = ({ params }) => { const [attributeCreate, attributeCreateOpts] = useAttributeCreateMutation({ onCompleted: data => { - if (data.attributeCreate.errors.length === 0) { + if (data?.attributeCreate?.errors.length === 0) { notify({ status: "success", text: intl.formatMessage({ @@ -94,7 +94,7 @@ const AttributeDetails: React.FC = ({ params }) => { defaultMessage: "Successfully created attribute", }), }); - navigate(attributeUrl(data.attributeCreate.attribute.id)); + navigate(attributeUrl(data?.attributeCreate?.attribute?.id ?? "")); } }, }); @@ -113,8 +113,10 @@ const AttributeDetails: React.FC = ({ params }) => { React.useEffect(() => setValueErrors([]), [params.action]); const handleValueDelete = () => { - const newValues = remove(values[id], values, areValuesEqual); - setValues(newValues); + if (id) { + const newValues = remove(values[id], values, areValuesEqual); + setValues(newValues); + } closeModal(); }; @@ -122,7 +124,9 @@ const AttributeDetails: React.FC = ({ params }) => { if (isSelected(input, values, areValuesEqual)) { setValueErrors([attributeValueAlreadyExistsError]); } else { - setValues(updateAtIndex(input, values, id)); + if (id) { + setValues(updateAtIndex(input, values, id)); + } closeModal(); } }; @@ -164,7 +168,7 @@ const AttributeDetails: React.FC = ({ params }) => { }); return { - id: result.data.attributeCreate?.attribute?.id || null, + id: result.data?.attributeCreate?.attribute?.id ?? undefined, errors: getMutationErrors(result), }; }; @@ -179,8 +183,8 @@ const AttributeDetails: React.FC = ({ params }) => { undefined} onSubmit={handleSubmit} onValueAdd={() => openModal("add-value")} onValueDelete={id => @@ -212,7 +216,7 @@ const AttributeDetails: React.FC = ({ params }) => { file: value?.fileUrl ? { url: value.fileUrl, - contentType: value.contentType, + contentType: value.contentType ?? "", __typename: "File", } : null, @@ -251,16 +255,16 @@ const AttributeDetails: React.FC = ({ params }) => { {values.length > 0 && ( <> = ({ id, params }) => { const [attributeDelete, attributeDeleteOpts] = useAttributeDeleteMutation({ onCompleted: data => { - if (data?.attributeDelete.errors.length === 0) { + if (data?.attributeDelete?.errors.length === 0) { notify({ status: "success", text: intl.formatMessage({ @@ -109,7 +109,7 @@ const AttributeDetails: React.FC = ({ id, params }) => { attributeValueDeleteOpts, ] = useAttributeValueDeleteMutation({ onCompleted: data => { - if (data?.attributeValueDelete.errors.length === 0) { + if (data?.attributeValueDelete?.errors.length === 0) { notify({ status: "success", text: intl.formatMessage({ @@ -128,7 +128,7 @@ const AttributeDetails: React.FC = ({ id, params }) => { attributeValueUpdateOpts, ] = useAttributeValueUpdateMutation({ onCompleted: data => { - if (data?.attributeValueUpdate.errors.length === 0) { + if (data?.attributeValueUpdate?.errors.length === 0) { notifySaved(); closeModal(); } @@ -137,7 +137,7 @@ const AttributeDetails: React.FC = ({ id, params }) => { const [attributeUpdate, attributeUpdateOpts] = useAttributeUpdateMutation({ onCompleted: data => { - if (data?.attributeUpdate.errors.length === 0) { + if (data?.attributeUpdate?.errors.length === 0) { notifySaved(); } }, @@ -148,7 +148,7 @@ const AttributeDetails: React.FC = ({ id, params }) => { attributeValueCreateOpts, ] = useAttributeValueCreateMutation({ onCompleted: data => { - if (data?.attributeValueCreate.errors.length === 0) { + if (data?.attributeValueCreate?.errors.length === 0) { notify({ status: "success", text: intl.formatMessage({ @@ -164,11 +164,11 @@ const AttributeDetails: React.FC = ({ id, params }) => { const [attributeValueReorder] = useAttributeValueReorderMutation({ onCompleted: data => { - if (data?.attributeReorderValues.errors.length !== 0) { + if (data?.attributeReorderValues?.errors.length !== 0) { notify({ status: "error", text: getAttributeErrorMessage( - data?.attributeReorderValues.errors[0], + data?.attributeReorderValues?.errors[0], intl, ), }); @@ -185,16 +185,16 @@ const AttributeDetails: React.FC = ({ id, params }) => { attributeReorderValues: { __typename: "AttributeReorderValues", attribute: { - ...data?.attribute, + ...data?.attribute!, choices: { __typename: "AttributeValueCountableConnection", pageInfo: { - ...data?.attribute.choices.pageInfo, + ...data?.attribute?.choices?.pageInfo!, }, edges: move( - data?.attribute.choices.edges[oldIndex], - data?.attribute.choices.edges, - (a, b) => a.node.id === b.node.id, + data?.attribute?.choices?.edges[oldIndex]!, + data?.attribute?.choices?.edges ?? [], + (a, b) => a?.node.id === b?.node.id, newIndex, ), }, @@ -205,7 +205,7 @@ const AttributeDetails: React.FC = ({ id, params }) => { variables: { id, move: { - id: data?.attribute.choices.edges[oldIndex].node.id, + id: data?.attribute?.choices?.edges[oldIndex].node.id ?? "", sortOrder: newIndex - oldIndex, }, firstValues: valuesPaginationState.first, @@ -237,7 +237,7 @@ const AttributeDetails: React.FC = ({ id, params }) => { ); const handleSubmit = createMetadataUpdateHandler( - data?.attribute, + data?.attribute!, handleUpdate, variables => updateMetadata({ variables }), variables => updatePrivateMetadata({ variables }), @@ -247,7 +247,7 @@ const AttributeDetails: React.FC = ({ id, params }) => { openModal("remove")} onSubmit={handleSubmit} onValueAdd={() => openModal("add-value")} @@ -266,7 +266,7 @@ const AttributeDetails: React.FC = ({ id, params }) => { values={data?.attribute?.choices} settings={settings} onUpdateListSettings={updateListSettings} - pageInfo={pageInfo} + pageInfo={pageInfo ?? { hasNextPage: false, hasPreviousPage: false }} onNextPage={loadNextPage} onPreviousPage={loadPreviousPage} > @@ -291,7 +291,7 @@ const AttributeDetails: React.FC = ({ id, params }) => { name={getStringOrPlaceholder( data?.attribute?.choices?.edges?.find( value => params.id === value.node.id, - )?.node.name, + )?.node?.name ?? "", )} useName={true} confirmButtonState={attributeValueDeleteOpts.status} @@ -299,7 +299,7 @@ const AttributeDetails: React.FC = ({ id, params }) => { onConfirm={() => attributeValueDelete({ variables: { - id: params.id, + id: params?.id ?? "", firstValues: valuesPaginationState.first, lastValues: valuesPaginationState.last, afterValues: valuesPaginationState.after, @@ -314,7 +314,7 @@ const AttributeDetails: React.FC = ({ id, params }) => { confirmButtonState={attributeValueCreateOpts.status} disabled={loading} errors={ - attributeValueCreateOpts.data?.attributeValueCreate.errors || [] + attributeValueCreateOpts.data?.attributeValueCreate?.errors || [] } open={params.action === "add-value"} onClose={closeModal} @@ -336,21 +336,22 @@ const AttributeDetails: React.FC = ({ id, params }) => { attributeValue={attributeValueFragmentToFormData( data?.attribute?.choices?.edges?.find( value => params.id === value.node.id, - )?.node, + )?.node ?? null, )} confirmButtonState={attributeValueUpdateOpts.status} disabled={loading} errors={ - attributeValueUpdateOpts.data?.attributeValueUpdate.errors || [] + attributeValueUpdateOpts.data?.attributeValueUpdate?.errors || [] } open={params.action === "edit-value"} onClose={closeModal} onSubmit={input => attributeValueUpdate({ variables: { - id: data?.attribute.choices.edges.find( - value => params.id === value.node.id, - ).node.id, + id: + data?.attribute?.choices?.edges?.find( + value => params.id === value.node.id, + )?.node?.id || "", input, firstValues: valuesPaginationState.first, lastValues: valuesPaginationState.last, diff --git a/src/attributes/views/AttributeList/AttributeList.tsx b/src/attributes/views/AttributeList/AttributeList.tsx index 136cb6274..f353a04b4 100644 --- a/src/attributes/views/AttributeList/AttributeList.tsx +++ b/src/attributes/views/AttributeList/AttributeList.tsx @@ -73,7 +73,7 @@ const AttributeList: React.FC = ({ params }) => { attributeBulkDeleteOpts, ] = useAttributeBulkDeleteMutation({ onCompleted: data => { - if (data.attributeBulkDelete.errors.length === 0) { + if (data.attributeBulkDelete?.errors.length === 0) { closeModal(); notify({ status: "success", @@ -132,7 +132,7 @@ const AttributeList: React.FC = ({ params }) => { }; const paginationValues = usePaginator({ - pageInfo: maybe(() => data.attributes.pageInfo), + pageInfo: data?.attributes?.pageInfo, paginationState, queryString: params, }); @@ -142,7 +142,7 @@ const AttributeList: React.FC = ({ params }) => { return ( = ({ params }) => { /> params.ids.length > 0)} + open={ + params.action === "remove" && !!params.ids && params.ids.length > 0 + } onConfirm={() => - attributeBulkDelete({ variables: { ids: params.ids } }) + attributeBulkDelete({ variables: { ids: params?.ids ?? [] } }) } onClose={closeModal} - quantity={maybe(() => params.ids.length)} + quantity={params.ids?.length ?? 0} /> parseBoolean(params.filterableInStorefront, true)), + value: parseBoolean(params.filterableInStorefront, true), }, isVariantOnly: { active: params.isVariantOnly !== undefined, - value: maybe(() => parseBoolean(params.isVariantOnly, true)), + value: parseBoolean(params.isVariantOnly, true), }, valueRequired: { active: params.valueRequired !== undefined, - value: maybe(() => parseBoolean(params.valueRequired, true)), + value: parseBoolean(params.valueRequired, true), }, visibleInStorefront: { active: params.visibleInStorefront !== undefined, - value: maybe(() => parseBoolean(params.visibleInStorefront, true)), + value: parseBoolean(params.visibleInStorefront, true), }, }; } diff --git a/src/attributes/views/AttributeList/sort.ts b/src/attributes/views/AttributeList/sort.ts index dde0f8cfe..8a1390150 100644 --- a/src/attributes/views/AttributeList/sort.ts +++ b/src/attributes/views/AttributeList/sort.ts @@ -17,7 +17,7 @@ export function getSortQueryField( case AttributeListUrlSortField.visible: return AttributeSortField.VISIBLE_IN_STOREFRONT; default: - return undefined; + return AttributeSortField.NAME; } } diff --git a/src/misc.ts b/src/misc.ts index b61df2193..7a572bfcf 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -431,7 +431,10 @@ export function findValueInEnum( return needle as unknown as TEnum[keyof TEnum]; } -export function parseBoolean(a: string, defaultValue: boolean): boolean { +export function parseBoolean( + a: string | undefined, + defaultValue: boolean, +): boolean { if (a === undefined) { return defaultValue; }