Fix strict null errors in utils (#3007)

* Fix strict null in utils/errors

* Fix auth-related src/utils strict null errors

* Fix data, maps and tables utils

* Fix utils/urls

* Fix filters

* Fix strict null errors in utils/handlers

* Fix strict nulls in utils/menu

* Fix strict nulls in utils/sort

* Fix strict nulls in utils/richText

* Fix search handler

* Fix siteSettings leftover type mismatch

* Fix page map error for attribute case

* Fix type error for sdk error codes

* Fix Choice type in page maps

* Fix error types mismatch

* Remove addressType from type union in auth error message
This commit is contained in:
Michał Droń 2023-01-23 09:30:04 +01:00 committed by GitHub
parent 221adf25d6
commit abbe76442d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 121 additions and 90 deletions

View file

@ -6,7 +6,7 @@ import makeQuery, { UseQueryResult } from "./makeQuery";
import useDebounce from "./useDebounce";
export interface SearchVariables {
after?: string;
after?: string | null;
first: number;
query: string;
}

View file

@ -9,7 +9,7 @@ import SiteSettingsPage, { SiteSettingsPageProps } from "./SiteSettingsPage";
const props: Omit<SiteSettingsPageProps, "classes"> = {
disabled: false,
errors: [],
onSubmit: () => undefined,
onSubmit: async () => undefined,
saveButtonBarState: "default",
shop,
};

View file

@ -7,6 +7,6 @@ export enum GqlErrors {
export function hasError(err: ApolloError, ...errorCodes: string[]): boolean {
return err.graphQLErrors.some(gqlError =>
errorCodes.includes(gqlError.extensions.exception.code),
errorCodes.includes(gqlError.extensions?.exception.code),
);
}

View file

@ -1,31 +1,30 @@
import { LoginData } from "@saleor/sdk";
import { UserFragment } from "@dashboard/graphql";
import { UserDetailsFragment } from "@saleor/sdk/dist/apollo/types";
export const isSupported = !!(
navigator?.credentials?.preventSilentAccess && window.PasswordCredential
);
export const isSupported = !!window.PasswordCredential;
export async function login<T>(
loginFn: (id: string, password: string) => Promise<T>,
): Promise<T | null> {
let result: T;
let result: T | null;
try {
const credential = await navigator.credentials.get({ password: true });
if (credential instanceof PasswordCredential) {
result = await loginFn(credential.id, credential.password);
result = await loginFn(credential.id, credential.password ?? "");
}
} catch {
result = null;
}
return result;
return result!;
}
export function saveCredentials(
user: LoginData["user"],
user: UserFragment | UserDetailsFragment,
password: string,
): Promise<CredentialType | null> {
let result: Promise<CredentialType | null>;
): Promise<CredentialType> | null {
let result: Promise<CredentialType> | null;
if (isSupported) {
const cred = new PasswordCredential({

View file

@ -1,5 +1,5 @@
interface PublicationData {
publicationDate: string;
publicationDate: string | null;
isPublished: boolean;
}

View file

@ -1,5 +1,8 @@
import { AccountErrorCode } from "@dashboard/graphql";
import { SetPasswordData } from "@saleor/sdk";
import {
AccountError,
AccountErrorCode as SdkAccountErrorCode,
} from "@saleor/sdk/dist/apollo/types";
import { defineMessages, IntlShape } from "react-intl";
import { getCommonFormFieldErrorMessage } from "./common";
@ -48,11 +51,14 @@ const messages = defineMessages({
});
interface ErrorFragment {
code: AccountErrorCode | SetPasswordData["errors"][number]["code"];
code: AccountErrorCode;
field: string | null;
}
function getAccountErrorMessage(err: ErrorFragment, intl: IntlShape): string {
function getAccountErrorMessage(
err: ErrorFragment | Omit<AccountError, "addressType">,
intl: IntlShape,
): string | undefined {
if (err) {
switch (err.code) {
case AccountErrorCode.INVALID_PASSWORD:
@ -78,7 +84,10 @@ function getAccountErrorMessage(err: ErrorFragment, intl: IntlShape): string {
}
}
return getCommonFormFieldErrorMessage(err, intl);
return getCommonFormFieldErrorMessage<AccountErrorCode | SdkAccountErrorCode>(
err,
intl,
);
}
export default getAccountErrorMessage;

View file

@ -38,7 +38,10 @@ const messages = defineMessages({
},
});
function getAppErrorMessage(err: AppErrorFragment, intl: IntlShape): string {
function getAppErrorMessage(
err: AppErrorFragment,
intl: IntlShape,
): string | undefined {
if (err) {
switch (err.code) {
case AppErrorCode.INVALID_MANIFEST_FORMAT:

View file

@ -21,7 +21,7 @@ const messages = defineMessages({
function getAttributeErrorMessage(
err: Omit<AttributeErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case AttributeErrorCode.ALREADY_EXISTS:

View file

@ -21,7 +21,7 @@ const messages = defineMessages({
function getChannelsErrorMessage(
err: Omit<ChannelErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case ChannelErrorCode.ALREADY_EXISTS:

View file

@ -33,7 +33,7 @@ export interface CommonError<ErrorCode> {
export function getCommonFormFieldErrorMessage<ErrorCode>(
error: CommonError<ErrorCode> | undefined,
intl: IntlShape,
): string {
): string | undefined {
if (error) {
switch (error.code) {
case "GRAPHQL_ERROR":

View file

@ -14,7 +14,7 @@ const messages = defineMessages({
function getDiscountErrorMessage(
err: Omit<DiscountErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case DiscountErrorCode.ALREADY_EXISTS:

View file

@ -6,7 +6,7 @@ import { getCommonFormFieldErrorMessage } from "./common";
function getExportErrorMessage(
err: Omit<ExportErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
return getCommonFormFieldErrorMessage(err, intl);
}

View file

@ -3,20 +3,14 @@ import { UserError } from "@dashboard/types";
export function getFieldError<T extends UserError>(
errors: T[],
field: string,
): T {
): T | undefined {
return errors.find(err => err.field === field);
}
export function getErrors(errors: UserError[]): string[] {
return errors
.filter(err => ["", null].includes(err.field))
.map(err => err.message);
}
export type FormErrors<
TField extends string,
TError extends UserError
> = Record<TField, TError>;
> = Record<TField, TError | undefined>;
export function getFormErrors<TField extends string, TError extends UserError>(
fields: TField[],
@ -25,7 +19,7 @@ export function getFormErrors<TField extends string, TError extends UserError>(
return fields.reduce((errs, field) => {
errs[field] = getFieldError(errors, field);
return errs;
}, ({} as unknown) as Record<TField, TError>);
}, ({} as unknown) as Record<TField, TError | undefined>);
}
export interface ChannelError {

View file

@ -40,7 +40,7 @@ const messages = defineMessages({
function getInvoiceErrorMessage(
err: InvoiceErrorFragment,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case InvoiceErrorCode.EMAIL_NOT_SET:

View file

@ -6,7 +6,7 @@ import { getCommonFormFieldErrorMessage } from "./common";
function getMenuErrorMessage(
err: Omit<MenuErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
return getCommonFormFieldErrorMessage(err, intl);
}

View file

@ -75,7 +75,7 @@ const messages = defineMessages({
function getOrderErrorMessage(
err: OrderErrorFragment,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case OrderErrorCode.BILLING_ADDRESS_NOT_SET:

View file

@ -29,7 +29,7 @@ const messages = defineMessages({
function getPageErrorMessage(
err: Omit<PageErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case PageErrorCode.UNIQUE:

View file

@ -32,7 +32,7 @@ const messages = defineMessages({
function getPermissionGroupErrorMessage(
err: PermissionGroupErrorFragment,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case PermissionGroupErrorCode.ASSIGN_NON_STAFF_MEMBER:

View file

@ -17,7 +17,7 @@ const messages = defineMessages({
function getPluginErrorMessage(
err: PluginErrorFragment,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case PluginErrorCode.PLUGIN_MISCONFIGURED:

View file

@ -83,7 +83,7 @@ function getProductErrorMessage(
>
| undefined,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case ProductErrorCode.ATTRIBUTE_ALREADY_ASSIGNED:
@ -119,7 +119,7 @@ function getProductErrorMessage(
export function getProductVariantAttributeErrorMessage(
err: Omit<ProductErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case ProductErrorCode.UNIQUE:
@ -135,7 +135,7 @@ export function getProductVariantAttributeErrorMessage(
export function getBulkProductErrorMessage(
err: BulkProductErrorFragment | undefined,
intl: IntlShape,
): string {
): string | undefined {
if (err?.code === ProductErrorCode.UNIQUE && err.field === "sku") {
return intl.formatMessage(messages.skuUnique);
}

View file

@ -19,7 +19,7 @@ const messages = defineMessages({
function getShippingErrorMessage(
err: Omit<ShippingErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case ShippingErrorCode.ALREADY_EXISTS:

View file

@ -14,7 +14,7 @@ const messages = defineMessages({
function getShopErrorMessage(
err: Omit<ShopErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case ShopErrorCode.ALREADY_EXISTS:

View file

@ -6,7 +6,7 @@ import getAccountErrorMessage from "./account";
function getStaffErrorMessage(
err: StaffErrorFragment,
intl: IntlShape,
): string {
): string | undefined {
return getAccountErrorMessage(err, intl);
}

View file

@ -20,7 +20,7 @@ const messages = defineMessages({
function getStockErrorMessage(
err: Omit<StockErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case StockErrorCode.UNIQUE:
@ -34,7 +34,7 @@ function getStockErrorMessage(
export function getBulkStockErrorMessage(
err: Omit<BulkStockErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
return getProductErrorMessage(err, intl);
}

View file

@ -20,7 +20,7 @@ export type TaxClassError =
function getTaxesErrorMessage(
err: Omit<TaxClassError, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
return getCommonFormFieldErrorMessage(err, intl);
}

View file

@ -14,14 +14,14 @@ const messages = defineMessages({
function getWarehouseErrorMessage(
err: Omit<WarehouseErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
return getCommonFormFieldErrorMessage(err, intl);
}
export function getWarehouseSlugErrorMessage(
err: Omit<WarehouseErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
if (err) {
switch (err.code) {
case WarehouseErrorCode.UNIQUE:

View file

@ -6,7 +6,7 @@ import { getCommonFormFieldErrorMessage } from "./common";
function getWebhookErrorMessage(
err: Omit<WebhookErrorFragment, "__typename"> | undefined,
intl: IntlShape,
): string {
): string | undefined {
return getCommonFormFieldErrorMessage(err, intl);
}

View file

@ -161,6 +161,6 @@ export function createBooleanField<K extends string>(
},
],
type: FieldType.boolean,
value: [defaultValue?.toString()],
value: [defaultValue?.toString() ?? ""],
};
}

View file

@ -91,7 +91,7 @@ export function getSingleValueQueryParam<
>(param: FilterElement<TKey>, key: TUrlKey) {
const { active, value } = param;
if (!active) {
if (!active || !value) {
return {
[key]: undefined,
};
@ -161,7 +161,7 @@ export function getMinMaxQueryParam<
>(param: FilterElement<TKey>, keyFrom: TUrlKey, keyTo: TUrlKey) {
const { active, multiple, value } = param;
if (!active) {
if (!active || !value) {
return {
[keyFrom]: undefined,
[keyTo]: undefined,

View file

@ -8,7 +8,8 @@ export type GetFilterTabsOutput<TUrlFilters> = Array<UserFilter<TUrlFilters>>;
function getFilterTabs<TUrlFilters>(
key: string,
): GetFilterTabsOutput<TUrlFilters> {
return JSON.parse(localStorage.getItem(key)) || [];
const filterTabs = localStorage.getItem(key);
return filterTabs ? JSON.parse(filterTabs) : [];
}
function saveFilterTab<TUrlFilters>(

View file

@ -20,7 +20,7 @@ function createMetadataCreateHandler<T extends MetadataFormData, TError>(
return async (data: T) => {
const { id, errors } = await create(data);
if (id === null || !!errors?.length) {
if (!id || !!errors?.length) {
return errors;
}
@ -33,8 +33,8 @@ function createMetadataCreateHandler<T extends MetadataFormData, TError>(
},
});
const updateMetaErrors = [
...(updateMetaResult.data.deleteMetadata.errors || []),
...(updateMetaResult.data.updateMetadata.errors || []),
...(updateMetaResult.data?.deleteMetadata?.errors || []),
...(updateMetaResult.data?.updateMetadata?.errors || []),
];
if (updateMetaErrors.length > 0) {
@ -52,8 +52,8 @@ function createMetadataCreateHandler<T extends MetadataFormData, TError>(
});
const updatePrivateMetaErrors = [
...(updatePrivateMetaResult.data.deletePrivateMetadata.errors || []),
...(updatePrivateMetaResult.data.updatePrivateMetadata.errors || []),
...(updatePrivateMetaResult.data?.deletePrivateMetadata?.errors || []),
...(updatePrivateMetaResult.data?.updatePrivateMetadata?.errors || []),
];
if (updatePrivateMetaErrors.length > 0) {

View file

@ -62,8 +62,8 @@ function createMetadataUpdateHandler<TData extends MetadataFormData, TError>(
});
const updateMetaErrors = [
...(updateMetaResult.data.deleteMetadata.errors || []),
...(updateMetaResult.data.updateMetadata.errors || []),
...(updateMetaResult.data?.deleteMetadata?.errors || []),
...(updateMetaResult.data?.updateMetadata?.errors || []),
];
if (updateMetaErrors.length > 0) {
@ -84,8 +84,10 @@ function createMetadataUpdateHandler<TData extends MetadataFormData, TError>(
});
const updatePrivateMetaErrors = [
...(updatePrivateMetaResult.data.deletePrivateMetadata.errors || []),
...(updatePrivateMetaResult.data.updatePrivateMetadata.errors || []),
...(updatePrivateMetaResult.data?.deletePrivateMetadata?.errors ||
[]),
...(updatePrivateMetaResult.data?.updatePrivateMetadata?.errors ||
[]),
];
if (updatePrivateMetaErrors.length > 0) {

View file

@ -20,6 +20,10 @@ function createMultiAutocompleteSelectHandler(
const id = event.target.value;
const choice = combinedChoices.find(choice => choice.value === id);
if (!choice) {
return;
}
setSelected(toggle(choice, selected, (a, b) => a.value === b.value));
};
}

View file

@ -8,10 +8,11 @@ import {
CountryWithCodeFragment,
MetadataInput,
MetadataItemFragment,
SearchPagesQuery,
PageFragment,
} from "@dashboard/graphql";
import { getFullName } from "@dashboard/misc";
import { Node, RelayToFlat, SlugNode, TagNode } from "@dashboard/types";
import { Node, SlugNode, TagNode } from "@dashboard/types";
import { Choice } from "@saleor/macaw-ui";
interface Edge<T> {
node: T;
@ -40,8 +41,8 @@ export function mapCountriesToChoices(countries: CountryWithCodeFragment[]) {
}
export function mapPagesToChoices(
pages: RelayToFlat<SearchPagesQuery["search"]>,
) {
pages: Array<Pick<PageFragment, "title" | "id">>,
): Choice[] {
return pages.map(page => ({
label: page.title,
value: page.id,
@ -105,6 +106,10 @@ export function mapMultiValueNodeToChoice<T extends Record<string, any>>(
return (nodes as string[]).map(node => ({ label: node, value: node }));
}
if (!key) {
return [];
}
return (nodes as T[]).map(node => ({ label: node[key], value: node[key] }));
}
@ -120,6 +125,10 @@ export function mapSingleValueNodeToChoice<T extends Record<string, any>>(
return (nodes as string[]).map(node => ({ label: node, value: node }));
}
if (!key) {
return [];
}
return (nodes as T[]).map(node => ({ label: node[key], value: node[key] }));
}

View file

@ -27,9 +27,11 @@ export type IFlatMenu<TMenuData = {}, TValue = string> = Array<
export function validateMenuOptions<TMenuData = {}, TValue = string>(
menu: IMenu<TMenuData, TValue>,
): boolean {
const isValue = (val: TValue | undefined): val is TValue => val !== undefined;
const values: TValue[] = toFlat(menu)
.map(menuItem => menuItem.value)
.filter(value => value !== undefined);
.filter(isValue);
const uniqueValues = Array.from(new Set(values));
return uniqueValues.length === values.length;
}
@ -56,9 +58,9 @@ export function getMenuItemByValue<TMenuData = {}, TValue = string>(
value: TValue,
): IMenuItem<TMenuData, TValue> {
const flatMenu = toFlat(menu);
const flatMenuItem: IFlatMenuItem<TMenuData, TValue> = flatMenu.find(
menuItem => menuItem.value === value,
);
const flatMenuItem:
| IFlatMenuItem<TMenuData, TValue>
| undefined = flatMenu.find(menuItem => menuItem.value === value);
if (flatMenuItem === undefined) {
throw new Error(`Value ${value} does not exist in menu`);
@ -94,6 +96,10 @@ function _walkToRoot<TMenuData = {}, TValue = string>(
): IFlatMenu<TMenuData, TValue> {
const menuItem = flatMenu.find(menuItem => menuItem.id === parent);
if (menuItem === undefined) {
throw new Error(`Value ${parent} does not exist in menu`);
}
if (menuItem.parent === null) {
return [menuItem];
}
@ -107,6 +113,10 @@ export function walkToRoot<TMenuData = {}, TValue = string>(
const flatMenu = toFlat(menu);
const menuItem = flatMenu.find(menuItem => menuItem.value === value);
if (menuItem === undefined) {
throw new Error(`Value ${value} does not exist in menu`);
}
return (menuItem.parent === null
? [menuItem]
: [menuItem, ..._walkToRoot(flatMenu, menuItem.parent)]
@ -116,7 +126,7 @@ export function walkToRoot<TMenuData = {}, TValue = string>(
function _toFlat<TMenuData = {}, TValue = string>(
menuItem: IMenuItem<TMenuData, TValue>,
sort: number,
parent: string,
parent: string | null,
): IFlatMenu<TMenuData, TValue> {
const id = parent ? [parent, sort].join(":") : sort.toString();
const flatMenuItem: IFlatMenuItem<TMenuData, TValue> = {

View file

@ -1,19 +1,27 @@
import { EditorCore } from "@dashboard/components/RichTextEditor";
import { OutputData } from "@editorjs/editorjs";
import { useMemo, useRef, useState } from "react";
import { MutableRefObject, useMemo, useRef, useState } from "react";
interface UseRichTextOptions {
initial: string | null;
initial: string | null | undefined;
loading?: boolean;
triggerChange: () => void;
}
interface UseRichTextResult {
editorRef: MutableRefObject<EditorCore | null>;
handleChange: () => void;
getValue: () => Promise<OutputData>;
defaultValue: OutputData | undefined;
isReadyForMount: boolean;
}
export function useRichText({
initial,
loading,
triggerChange,
}: UseRichTextOptions) {
const editorRef = useRef<EditorCore>(null);
}: UseRichTextOptions): UseRichTextResult {
const editorRef = useRef<EditorCore | null>(null);
const [isReadyForMount, setIsReadyForMount] = useState(false);
const handleChange = () => {
@ -33,7 +41,7 @@ export function useRichText({
return;
}
if (initial === undefined) {
if (!initial) {
setIsReadyForMount(true);
return "";
}

View file

@ -72,7 +72,7 @@ type GetSortQueryField<TUrlField extends string, TSortField extends string> = (
type GetSortQueryVariables<
TSortField extends string,
TParams extends Record<any, any>
> = (params: TParams) => SortingInput<TSortField>;
> = (params: TParams) => SortingInput<TSortField> | undefined;
export function createGetSortQueryVariables<
TUrlField extends string,
TSortField extends string,

View file

@ -1,10 +0,0 @@
export function getFooterColSpanWithBulkActions(
arr: any[],
numberOfColumns: number,
): number {
if (arr === undefined || arr.length > 0) {
return numberOfColumns + 1;
}
return numberOfColumns;
}

View file

@ -8,7 +8,9 @@ export function stringifyQs(params: unknown, arrayFormat?: string): string {
});
}
export function getArrayQueryParam(param: string | string[]): string[] {
export function getArrayQueryParam(
param: string | string[],
): string[] | undefined {
if (!param) {
return undefined;
}