Fix strict null checks in warehouses (#2945)

* Fix strict nulls in warehouse details

* Fix strict null check in warehouse list

* Fix stcit null checks in warehouse create view

* Fix overlapping label in textfields

* Improve labels in warehouse details

* Improve function description
This commit is contained in:
Michał Droń 2023-01-18 10:23:24 +01:00 committed by GitHub
parent 53806abc10
commit 931467a73a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 110 additions and 97 deletions

View file

@ -137,10 +137,6 @@
"+xTpT1": {
"string": "Attributes"
},
"//iaFx": {
"context": "WarehouseSettings title",
"string": "Settings"
},
"//k1GX": {
"context": "expired on label",
"string": "Expired on {date}"
@ -1441,6 +1437,10 @@
"context": "bulk issue menu item",
"string": "Bulk Issue"
},
"9kzIFd": {
"context": "WarehouseSettings stock title",
"string": "Stock settings"
},
"9mGA/Q": {
"context": "button linking to dashboard",
"string": "Dashboard"

View file

@ -4,10 +4,10 @@ import { createContext, useContext, useMemo } from "react";
import { Pagination } from "../types";
export interface PageInfo {
endCursor: string;
endCursor: string | null;
hasNextPage: boolean;
hasPreviousPage: boolean;
startCursor: string;
startCursor: string | null;
}
export interface PaginationState {
@ -37,7 +37,7 @@ export function createPaginationState(
}
interface UsePaginatorArgs {
pageInfo: PageInfo;
pageInfo: PageInfo | undefined;
paginationState: PaginationState;
queryString: Pagination;
}
@ -99,12 +99,7 @@ function usePaginator({
export default usePaginator;
export interface PaginatorContextValuesCommon {
hasNextPage?: boolean;
hasPreviousPage?: boolean;
endCursor?: string | null;
startCursor?: string | null;
}
export type PaginatorContextValuesCommon = Partial<PageInfo>;
export type PaginatorContextValues = PaginatorContextValuesCommon &
(

View file

@ -43,11 +43,11 @@ export type RequireOnlyOne<T, Keys extends keyof T = keyof T> = Pick<
}[Keys];
export function renderCollection<T>(
collection: T[],
collection: T[] | undefined,
renderItem: (
item: T | undefined,
index: number | undefined,
collection: T[],
collection: T[] | undefined,
) => any,
renderEmpty?: (collection: T[]) => any,
) {

View file

@ -4,7 +4,7 @@ import { ActiveTab, Pagination, Search, Sort } from "@dashboard/types";
import { GetFilterQueryParam, getFilterQueryParams } from "../filters";
type RequiredParams = ActiveTab & Search & Sort & Pagination;
type RequiredParams = ActiveTab & Search & Sort<any> & Pagination;
type CreateUrl = (params: RequiredParams) => string;
type CreateFilterHandlers<TFilterKeys extends string> = [
(filter: IFilter<TFilterKeys>) => void,

View file

@ -1,7 +1,7 @@
import { LimitInfoFragment, RefreshLimitsQuery } from "@dashboard/graphql";
export function hasLimits(
limits: RefreshLimitsQuery["shop"]["limits"],
limits: RefreshLimitsQuery["shop"]["limits"] | undefined,
key: keyof LimitInfoFragment,
): boolean {
if (limits === undefined) {
@ -10,14 +10,24 @@ export function hasLimits(
return limits.allowedUsage[key] !== null;
}
/**
* Returns whether or not limit has been reached.
* If limits are undefined, returns false.
* */
export function isLimitReached(
limits: RefreshLimitsQuery["shop"]["limits"],
limits: RefreshLimitsQuery["shop"]["limits"] | undefined,
key: keyof LimitInfoFragment,
): boolean {
if (!hasLimits(limits, key)) {
return false;
}
return limits.currentUsage[key] >= limits.allowedUsage[key];
const currentUsage = limits?.currentUsage[key];
const allowedUsage = limits?.allowedUsage[key];
if (!currentUsage || !allowedUsage) {
return false;
}
return currentUsage >= allowedUsage!;
}

View file

@ -17,7 +17,7 @@ interface Edge<T> {
node: T;
}
interface Connection<T> {
edges: Array<Edge<T>> | undefined;
edges: Array<Edge<T>> | undefined | null;
}
export function mapEdgesToItems<T>(

View file

@ -17,7 +17,7 @@ const props: WarehouseCreatePageProps = {
})),
disabled: false,
errors: [],
onSubmit: () => undefined,
onSubmit: async () => undefined,
saveButtonBarState: "default",
};
storiesOf("Warehouses / Create warehouse", module)

View file

@ -19,7 +19,7 @@ const props: WarehouseDetailsPageProps = {
disabled: false,
errors: [],
onDelete: () => undefined,
onSubmit: () => undefined,
onSubmit: async () => undefined,
saveButtonBarState: "default",
warehouse,
};

View file

@ -9,7 +9,6 @@ import PageHeader from "@dashboard/components/PageHeader";
import Savebar from "@dashboard/components/Savebar";
import { AddressTypeInput } from "@dashboard/customers/types";
import {
CountryCode,
CountryWithCodeFragment,
WarehouseClickAndCollectOptionEnum,
WarehouseDetailsFragment,
@ -20,7 +19,6 @@ import { SubmitPromise } from "@dashboard/hooks/useForm";
import useNavigator from "@dashboard/hooks/useNavigator";
import useStateFromProps from "@dashboard/hooks/useStateFromProps";
import { sectionNames } from "@dashboard/intl";
import { findValueInEnum, maybe } from "@dashboard/misc";
import createSingleAutocompleteSelectHandler from "@dashboard/utils/handlers/singleAutocompleteSelectChangeHandler";
import { mapCountriesToChoices, mapEdgesToItems } from "@dashboard/utils/maps";
import { warehouseListUrl } from "@dashboard/warehouses/urls";
@ -41,7 +39,7 @@ export interface WarehouseDetailsPageProps {
disabled: boolean;
errors: WarehouseErrorFragment[];
saveButtonBarState: ConfirmButtonTransitionState;
warehouse: WarehouseDetailsFragment;
warehouse: WarehouseDetailsFragment | undefined;
onDelete: () => void;
onSubmit: (data: WarehouseDetailsPageFormData) => SubmitPromise;
}
@ -68,21 +66,19 @@ const WarehouseDetailsPage: React.FC<WarehouseDetailsPageProps> = ({
} = useAddressValidation(onSubmit);
const initialForm: WarehouseDetailsPageFormData = {
city: maybe(() => warehouse.address.city, ""),
companyName: maybe(() => warehouse.address.companyName, ""),
country: maybe(() =>
findValueInEnum(warehouse.address.country.code, CountryCode),
),
city: warehouse?.address.city ?? "",
companyName: warehouse?.address.companyName ?? "",
country: warehouse?.address.country.code ?? "",
isPrivate: !!warehouse?.isPrivate,
clickAndCollectOption:
warehouse?.clickAndCollectOption ||
WarehouseClickAndCollectOptionEnum.DISABLED,
countryArea: maybe(() => warehouse.address.countryArea, ""),
name: maybe(() => warehouse.name, ""),
phone: maybe(() => warehouse.address.phone, ""),
postalCode: maybe(() => warehouse.address.postalCode, ""),
streetAddress1: maybe(() => warehouse.address.streetAddress1, ""),
streetAddress2: maybe(() => warehouse.address.streetAddress2, ""),
countryArea: warehouse?.address.countryArea ?? "",
name: warehouse?.name ?? "",
phone: warehouse?.address.phone ?? "",
postalCode: warehouse?.address.postalCode ?? "",
streetAddress1: warehouse?.address.streetAddress1 ?? "",
streetAddress2: warehouse?.address.streetAddress2 ?? "",
};
return (
@ -133,7 +129,7 @@ const WarehouseDetailsPage: React.FC<WarehouseDetailsPageProps> = ({
</div>
<div>
<WarehouseSettings
zones={mapEdgesToItems(warehouse?.shippingZones)}
zones={mapEdgesToItems(warehouse?.shippingZones) ?? []}
data={data}
disabled={disabled}
onChange={change}
@ -142,7 +138,7 @@ const WarehouseDetailsPage: React.FC<WarehouseDetailsPageProps> = ({
</div>
</Grid>
<Savebar
disabled={isSaveDisabled}
disabled={!!isSaveDisabled}
onCancel={() => navigate(warehouseListUrl())}
onDelete={onDelete}
onSubmit={submit}

View file

@ -5,7 +5,7 @@ import TableCellHeader from "@dashboard/components/TableCellHeader";
import { TablePaginationWithContext } from "@dashboard/components/TablePagination";
import TableRowLink from "@dashboard/components/TableRowLink";
import { WarehouseWithShippingFragment } from "@dashboard/graphql";
import { maybe, renderCollection, stopPropagation } from "@dashboard/misc";
import { renderCollection, stopPropagation } from "@dashboard/misc";
import { ListProps, SortPage } from "@dashboard/types";
import { mapEdgesToItems } from "@dashboard/utils/maps";
import { getArrowDirection } from "@dashboard/utils/sort";
@ -64,8 +64,8 @@ const useStyles = makeStyles(
interface WarehouseListProps
extends ListProps,
SortPage<WarehouseListUrlSortField> {
warehouses: WarehouseWithShippingFragment[];
onRemove: (id: string) => void;
warehouses: WarehouseWithShippingFragment[] | undefined;
onRemove: (id: string | undefined) => void;
}
const numberOfColumns = 3;
@ -90,7 +90,7 @@ const WarehouseList: React.FC<WarehouseListProps> = props => {
<TableCellHeader
direction={
sort.sort === WarehouseListUrlSortField.name
? getArrowDirection(sort.asc)
? getArrowDirection(!!sort.asc)
: undefined
}
arrowPosition="right"
@ -136,7 +136,7 @@ const WarehouseList: React.FC<WarehouseListProps> = props => {
}
>
<TableCell className={classes.colName} data-test-id="name">
{maybe<React.ReactNode>(() => warehouse.name, <Skeleton />)}
{warehouse?.name ?? <Skeleton />}
</TableCell>
<TableCell className={classes.colZones} data-test-id="zones">
{warehouse?.shippingZones === undefined ? (
@ -160,7 +160,7 @@ const WarehouseList: React.FC<WarehouseListProps> = props => {
<IconButton
variant="secondary"
color="primary"
onClick={stopPropagation(() => onRemove(warehouse.id))}
onClick={stopPropagation(() => onRemove(warehouse?.id))}
>
<DeleteIcon />
</IconButton>

View file

@ -32,8 +32,8 @@ export interface WarehouseListPageProps
SearchPageProps,
SortPage<WarehouseListUrlSortField>,
TabPageProps {
limits: RefreshLimitsQuery["shop"]["limits"];
warehouses: WarehouseWithShippingFragment[];
limits: RefreshLimitsQuery["shop"]["limits"] | undefined;
warehouses: WarehouseWithShippingFragment[] | undefined;
onRemove: (id: string) => void;
}
@ -66,18 +66,19 @@ export const WarehouseListPage: React.FC<WarehouseListPageProps> = ({
<PageHeader
title={intl.formatMessage(sectionNames.warehouses)}
limitText={
hasLimits(limits, "warehouses") &&
intl.formatMessage(
{
id: "YkOzse",
defaultMessage: "{count}/{max} warehouses used",
description: "used warehouses counter",
},
{
count: limits.currentUsage.warehouses,
max: limits.allowedUsage.warehouses,
},
)
hasLimits(limits, "warehouses")
? intl.formatMessage(
{
id: "YkOzse",
defaultMessage: "{count}/{max} warehouses used",
description: "used warehouses counter",
},
{
count: limits?.currentUsage.warehouses,
max: limits?.allowedUsage.warehouses,
},
)
: undefined
}
>
<Button

View file

@ -1,4 +1,3 @@
import CardSpacer from "@dashboard/components/CardSpacer";
import CardTitle from "@dashboard/components/CardTitle";
import { FormSpacer } from "@dashboard/components/FormSpacer";
import Link from "@dashboard/components/Link";
@ -9,6 +8,7 @@ import {
WarehouseClickAndCollectOptionEnum,
WarehouseWithShippingFragment,
} from "@dashboard/graphql";
import { sectionNames } from "@dashboard/intl";
import { renderCollection } from "@dashboard/misc";
import { shippingZoneUrl } from "@dashboard/shipping/urls";
import { RelayToFlat } from "@dashboard/types";
@ -146,9 +146,7 @@ const WarehouseSettings: React.FC<WarehouseSettingsProps> = ({
return (
<Card>
<CardTitle
title={<FormattedMessage {...messages.warehouseSettingsTitle} />}
/>
<CardTitle title={<FormattedMessage {...sectionNames.shippingZones} />} />
<CardContent>
{renderCollection(
zones,
@ -172,8 +170,10 @@ const WarehouseSettings: React.FC<WarehouseSettingsProps> = ({
)}
</CardContent>
<Divider />
<CardTitle
title={<FormattedMessage {...messages.warehouseSettingsStockTitle} />}
/>
<CardContent>
<CardSpacer />
<RadioGroupField
disabled={disabled}
choices={isPrivateChoices}
@ -184,12 +184,15 @@ const WarehouseSettings: React.FC<WarehouseSettingsProps> = ({
/>
</CardContent>
<Divider />
<CardTitle
title={
<>
<FormattedMessage {...messages.warehouseSettingsPickupTitle} />
<PreviewPill className={classes.preview} />
</>
}
/>
<CardContent>
<Typography color="textSecondary" variant="h6">
<FormattedMessage {...messages.warehouseSettingsPickupTitle} />
<PreviewPill className={classes.preview} />
</Typography>
<CardSpacer />
<RadioGroupField
disabled={disabled}
choices={

View file

@ -54,10 +54,10 @@ const messages = defineMessages({
"If selected customer will be able to choose this warehouse as pickup point. Ordered products can be shipped here from a different warehouse",
description: "WarehouseSettings all warehouses description",
},
warehouseSettingsTitle: {
id: "//iaFx",
defaultMessage: "Settings",
description: "WarehouseSettings title",
warehouseSettingsStockTitle: {
id: "9kzIFd",
defaultMessage: "Stock settings",
description: "WarehouseSettings stock title",
},
warehouseSettingsPickupTitle: {
id: "MIC9W7",

View file

@ -23,8 +23,11 @@ const WarehouseCreate: React.FC = () => {
const shop = useShop();
const [createWarehouse, createWarehouseOpts] = useWarehouseCreateMutation({
onCompleted: data => {
if (data.createWarehouse.errors.length === 0) {
navigate(warehouseUrl(data.createWarehouse.warehouse.id));
if (data?.createWarehouse?.errors.length === 0) {
const warehouse = data?.createWarehouse?.warehouse;
if (warehouse?.id) {
navigate(warehouseUrl(warehouse.id));
}
notify({
status: "success",
text: intl.formatMessage(commonMessages.savedChanges),
@ -68,7 +71,7 @@ const WarehouseCreate: React.FC = () => {
<WarehouseCreatePage
countries={shop?.countries || []}
disabled={createWarehouseOpts.loading}
errors={createWarehouseOpts.data?.createWarehouse.errors || []}
errors={createWarehouseOpts.data?.createWarehouse?.errors || []}
saveButtonBarState={createWarehouseTransitionState}
onSubmit={handleSubmit}
/>

View file

@ -45,7 +45,7 @@ const WarehouseDetails: React.FC<WarehouseDetailsProps> = ({ id, params }) => {
});
const [updateWarehouse, updateWarehouseOpts] = useWarehouseUpdateMutation({
onCompleted: data => {
if (data.updateWarehouse.errors.length === 0) {
if (data?.updateWarehouse?.errors.length === 0) {
notify({
status: "success",
text: intl.formatMessage(commonMessages.savedChanges),
@ -57,7 +57,7 @@ const WarehouseDetails: React.FC<WarehouseDetailsProps> = ({ id, params }) => {
const [deleteWarehouse, deleteWarehouseOpts] = useWarehouseDeleteMutation({
onCompleted: data => {
if (data.deleteWarehouse.errors.length === 0) {
if (data?.deleteWarehouse?.errors.length === 0) {
notify({
status: "success",
text: intl.formatMessage(commonMessages.savedChanges),
@ -105,11 +105,11 @@ const WarehouseDetails: React.FC<WarehouseDetailsProps> = ({ id, params }) => {
return (
<>
<WindowTitle title={data?.warehouse?.name} />
<WindowTitle title={getStringOrPlaceholder(data?.warehouse?.name)} />
<WarehouseDetailsPage
countries={shop?.countries || []}
disabled={loading || updateWarehouseOpts.loading}
errors={updateWarehouseOpts.data?.updateWarehouse.errors || []}
errors={updateWarehouseOpts.data?.updateWarehouse?.errors || []}
saveButtonBarState={updateWarehouseTransitionState}
warehouse={data?.warehouse}
onDelete={() => openModal("delete")}

View file

@ -78,7 +78,7 @@ const WarehouseList: React.FC<WarehouseListProps> = ({ params }) => {
});
const [deleteWarehouse, deleteWarehouseOpts] = useWarehouseDeleteMutation({
onCompleted: data => {
if (data.deleteWarehouse.errors.length === 0) {
if (data?.deleteWarehouse?.errors.length === 0) {
notify({
status: "success",
text: intl.formatMessage(commonMessages.savedChanges),
@ -96,7 +96,7 @@ const WarehouseList: React.FC<WarehouseListProps> = ({ params }) => {
const [, resetFilters, handleSearchChange] = createFilterHandlers({
createUrl: warehouseListUrl,
getFilterQueryParam: () => undefined,
getFilterQueryParam: async () => undefined,
navigate,
params,
});
@ -125,7 +125,7 @@ const WarehouseList: React.FC<WarehouseListProps> = ({ params }) => {
};
const paginationValues = usePaginator({
pageInfo: maybe(() => data.warehouses.pageInfo),
pageInfo: data?.warehouses?.pageInfo,
paginationState,
queryString: params,
});
@ -155,19 +155,24 @@ const WarehouseList: React.FC<WarehouseListProps> = ({ params }) => {
onUpdateListSettings={updateListSettings}
sort={getSortParams(params)}
/>
<WarehouseDeleteDialog
confirmButtonState={deleteTransitionState}
name={mapEdgesToItems(data?.warehouses)?.find(getById(params.id))?.name}
open={params.action === "delete"}
onClose={closeModal}
onConfirm={() =>
deleteWarehouse({
variables: {
id: params.id,
},
})
}
/>
{!!params.id && (
<WarehouseDeleteDialog
confirmButtonState={deleteTransitionState}
name={
mapEdgesToItems(data?.warehouses)?.find(getById(params.id))?.name ??
""
}
open={params.action === "delete"}
onClose={closeModal}
onConfirm={() =>
deleteWarehouse({
variables: {
id: params.id!,
},
})
}
/>
)}
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"

View file

@ -9,7 +9,7 @@ export function getSortQueryField(
case WarehouseListUrlSortField.name:
return WarehouseSortField.NAME;
default:
return undefined;
throw new Error("Invalid sort field");
}
}