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