Fix types
This commit is contained in:
parent
24cd398146
commit
f427edf550
15 changed files with 7 additions and 824 deletions
|
@ -19,9 +19,9 @@ import React from "react";
|
||||||
import SVG from "react-inlinesvg";
|
import SVG from "react-inlinesvg";
|
||||||
import { RouteComponentProps, withRouter } from "react-router";
|
import { RouteComponentProps, withRouter } from "react-router";
|
||||||
|
|
||||||
import saleorDarkLogoSmall from "@assets/logo-dark-small.svg";
|
import saleorDarkLogoSmall from "@assets/images/logo-dark-small.svg";
|
||||||
import saleorDarkLogo from "@assets/logo-dark.svg";
|
import saleorDarkLogo from "@assets/images/logo-dark.svg";
|
||||||
import menuArrowIcon from "@assets/menu-arrow-icon.svg";
|
import menuArrowIcon from "@assets/images/menu-arrow-icon.svg";
|
||||||
import AppProgressProvider from "@saleor/components/AppProgress";
|
import AppProgressProvider from "@saleor/components/AppProgress";
|
||||||
import useLocalStorage from "@saleor/hooks/useLocalStorage";
|
import useLocalStorage from "@saleor/hooks/useLocalStorage";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
import Button from "@material-ui/core/Button";
|
|
||||||
import Dialog from "@material-ui/core/Dialog";
|
|
||||||
import DialogActions from "@material-ui/core/DialogActions";
|
|
||||||
import DialogContent from "@material-ui/core/DialogContent";
|
|
||||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
|
||||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
|
||||||
import {
|
|
||||||
createStyles,
|
|
||||||
Theme,
|
|
||||||
withStyles,
|
|
||||||
WithStyles
|
|
||||||
} from "@material-ui/core/styles";
|
|
||||||
import React from "react";
|
|
||||||
import NavigationPrompt from "react-router-navigation-prompt";
|
|
||||||
|
|
||||||
import i18n from "../i18n";
|
|
||||||
import { FormContext } from "./Form";
|
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
|
||||||
createStyles({
|
|
||||||
deleteButton: {
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: theme.palette.error.main
|
|
||||||
},
|
|
||||||
backgroundColor: theme.palette.error.main,
|
|
||||||
color: theme.palette.error.contrastText
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ConfirmFormLeaveDialog = withStyles(styles, {
|
|
||||||
name: "ConfirmFormLeaveDialog"
|
|
||||||
})(({ classes }: WithStyles<typeof styles>) => (
|
|
||||||
<FormContext.Consumer>
|
|
||||||
{({ hasChanged: hasFormChanged }) => (
|
|
||||||
<NavigationPrompt renderIfNotActive={true} when={hasFormChanged}>
|
|
||||||
{({ isActive, onCancel, onConfirm }) => (
|
|
||||||
<Dialog onClose={onCancel} open={isActive}>
|
|
||||||
<DialogTitle>{i18n.t("Unsaved changes")}</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogContentText>
|
|
||||||
{i18n.t(
|
|
||||||
"If you leave this page, unsaved changes will be lost. Are you sure you want to leave?",
|
|
||||||
{
|
|
||||||
context: "form leave confirmation"
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</DialogContentText>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={onCancel}>
|
|
||||||
{i18n.t("Cancel", { context: "button" })}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={onConfirm} className={classes.deleteButton}>
|
|
||||||
{i18n.t("Leave page", { context: "button" })}
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
)}
|
|
||||||
</NavigationPrompt>
|
|
||||||
)}
|
|
||||||
</FormContext.Consumer>
|
|
||||||
));
|
|
|
@ -1,40 +0,0 @@
|
||||||
import Button from "@material-ui/core/Button";
|
|
||||||
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
|
|
||||||
import Toolbar from "@material-ui/core/Toolbar";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import i18n from "../../i18n";
|
|
||||||
|
|
||||||
const styles = createStyles({
|
|
||||||
cardActions: {
|
|
||||||
flexDirection: "row-reverse" as "row-reverse"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
interface FormActionsProps extends WithStyles<typeof styles> {
|
|
||||||
submitLabel: string;
|
|
||||||
onCancel?();
|
|
||||||
onSubmit?(event: React.FormEvent<any>);
|
|
||||||
}
|
|
||||||
|
|
||||||
const FormActions = withStyles(styles, { name: "FormActions" })(
|
|
||||||
({ classes, onCancel, onSubmit, submitLabel }: FormActionsProps) => (
|
|
||||||
<Toolbar className={classes.cardActions}>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
onClick={onSubmit}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
{submitLabel}
|
|
||||||
</Button>
|
|
||||||
{onCancel && (
|
|
||||||
<Button onClick={onCancel}>
|
|
||||||
{i18n.t("Cancel", { context: "button" })}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Toolbar>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
FormActions.displayName = "FormActions";
|
|
||||||
export default FormActions;
|
|
|
@ -1,54 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import FormComponent, { FormProps } from "./Form";
|
|
||||||
|
|
||||||
interface IFormContext {
|
|
||||||
hasChanged: boolean;
|
|
||||||
toggle: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FormContext = React.createContext<IFormContext>(undefined);
|
|
||||||
|
|
||||||
interface FormProviderState {
|
|
||||||
hasChanged: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FormProvider extends React.Component<{}, FormProviderState> {
|
|
||||||
state: FormProviderState = {
|
|
||||||
hasChanged: false
|
|
||||||
};
|
|
||||||
|
|
||||||
toggle = () =>
|
|
||||||
this.setState(prevState => ({
|
|
||||||
hasChanged: !prevState.hasChanged
|
|
||||||
}));
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<FormContext.Provider
|
|
||||||
value={{
|
|
||||||
hasChanged: this.state.hasChanged,
|
|
||||||
toggle: this.toggle
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{this.props.children}
|
|
||||||
</FormContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Form<T>(props: FormProps<T>) {
|
|
||||||
return (
|
|
||||||
<FormContext.Consumer>
|
|
||||||
{({ hasChanged, toggle }) => (
|
|
||||||
<FormComponent
|
|
||||||
{...props}
|
|
||||||
toggleFormChangeState={toggle}
|
|
||||||
hasChanged={hasChanged}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</FormContext.Consumer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Form;
|
|
|
@ -1,4 +1,2 @@
|
||||||
export * from "./Form";
|
export * from "./Form";
|
||||||
export { default } from "./Form";
|
export { default } from "./Form";
|
||||||
export { default as FormActions } from "./FormActions";
|
|
||||||
export * from "./FormActions";
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Avatar from "@material-ui/core/Avatar";
|
import Avatar from "@material-ui/core/Avatar";
|
||||||
import Card from "@material-ui/core/Card";
|
import Card from "@material-ui/core/Card";
|
||||||
import CardContent from "@material-ui/core/CardContent";
|
import CardContent from "@material-ui/core/CardContent";
|
||||||
import colors from "@material-ui/core/colors";
|
import * as colors from "@material-ui/core/colors";
|
||||||
import {
|
import {
|
||||||
createStyles,
|
createStyles,
|
||||||
Theme,
|
Theme,
|
||||||
|
|
|
@ -1,177 +0,0 @@
|
||||||
import Card from "@material-ui/core/Card";
|
|
||||||
import CardContent from "@material-ui/core/CardContent";
|
|
||||||
import {
|
|
||||||
createStyles,
|
|
||||||
Theme,
|
|
||||||
withStyles,
|
|
||||||
WithStyles
|
|
||||||
} from "@material-ui/core/styles";
|
|
||||||
import TextField from "@material-ui/core/TextField";
|
|
||||||
import Typography from "@material-ui/core/Typography";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
|
||||||
import ControlledSwitch from "@saleor/components/ControlledSwitch";
|
|
||||||
import FormSpacer from "@saleor/components/FormSpacer";
|
|
||||||
import Hr from "@saleor/components/Hr";
|
|
||||||
import TextFieldWithChoice from "@saleor/components/TextFieldWithChoice";
|
|
||||||
import i18n from "../../../i18n";
|
|
||||||
import { FormErrors } from "../../../types";
|
|
||||||
import { VoucherDiscountValueType } from "../../../types/globalTypes";
|
|
||||||
import { FormData } from "../VoucherDetailsPage";
|
|
||||||
|
|
||||||
interface VoucherOptionsProps {
|
|
||||||
data: FormData;
|
|
||||||
defaultCurrency: string;
|
|
||||||
disabled: boolean;
|
|
||||||
errors: FormErrors<
|
|
||||||
| "discountType"
|
|
||||||
| "discountValue"
|
|
||||||
| "endDate"
|
|
||||||
| "minAmountSpent"
|
|
||||||
| "startDate"
|
|
||||||
| "usageLimit"
|
|
||||||
>;
|
|
||||||
onChange: (event: React.ChangeEvent<any>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
|
||||||
createStyles({
|
|
||||||
root: {
|
|
||||||
display: "grid",
|
|
||||||
gridColumnGap: theme.spacing.unit * 2 + "px",
|
|
||||||
gridTemplateColumns: "1fr 1fr"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const VoucherOptions = withStyles(styles, {
|
|
||||||
name: "VoucherOptions"
|
|
||||||
})(
|
|
||||||
({
|
|
||||||
classes,
|
|
||||||
data,
|
|
||||||
defaultCurrency,
|
|
||||||
disabled,
|
|
||||||
errors,
|
|
||||||
onChange
|
|
||||||
}: VoucherOptionsProps & WithStyles<typeof styles>) => (
|
|
||||||
<Card>
|
|
||||||
<CardTitle title={i18n.t("Detailed Information")} />
|
|
||||||
<CardContent className={classes.root}>
|
|
||||||
<TextFieldWithChoice
|
|
||||||
disabled={disabled}
|
|
||||||
error={!!errors.discountValue}
|
|
||||||
ChoiceProps={{
|
|
||||||
label:
|
|
||||||
data.discountType === VoucherDiscountValueType.FIXED
|
|
||||||
? defaultCurrency
|
|
||||||
: "%",
|
|
||||||
name: "discountType" as keyof FormData,
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
label: defaultCurrency,
|
|
||||||
value: VoucherDiscountValueType.FIXED
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "%",
|
|
||||||
value: VoucherDiscountValueType.PERCENTAGE
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}}
|
|
||||||
helperText={errors.discountValue}
|
|
||||||
name={"value" as keyof FormData}
|
|
||||||
onChange={onChange}
|
|
||||||
label={i18n.t("Discount Value")}
|
|
||||||
value={data.value}
|
|
||||||
type="number"
|
|
||||||
fullWidth
|
|
||||||
inputProps={{
|
|
||||||
min: 0
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
disabled={disabled}
|
|
||||||
error={!!errors.usageLimit}
|
|
||||||
helperText={errors.usageLimit || i18n.t("Optional")}
|
|
||||||
name={"usageLimit" as keyof FormData}
|
|
||||||
value={data.usageLimit}
|
|
||||||
onChange={onChange}
|
|
||||||
label={i18n.t("Usage Limit")}
|
|
||||||
type="number"
|
|
||||||
inputProps={{
|
|
||||||
min: 0
|
|
||||||
}}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</CardContent>
|
|
||||||
<Hr />
|
|
||||||
<CardContent>
|
|
||||||
<Typography variant="subtitle1">
|
|
||||||
{i18n.t("Discount Specific Information")}
|
|
||||||
</Typography>
|
|
||||||
<FormSpacer />
|
|
||||||
<div className={classes.root}>
|
|
||||||
<TextField
|
|
||||||
disabled={disabled}
|
|
||||||
error={!!errors.minAmountSpent}
|
|
||||||
helperText={errors.minAmountSpent || i18n.t("Optional")}
|
|
||||||
name={"minAmountSpent" as keyof FormData}
|
|
||||||
value={data.minAmountSpent}
|
|
||||||
onChange={onChange}
|
|
||||||
label={i18n.t("Minimum order value")}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<FormSpacer />
|
|
||||||
<ControlledSwitch
|
|
||||||
checked={data.applyOncePerOrder}
|
|
||||||
label={
|
|
||||||
<>
|
|
||||||
{i18n.t("Only once per order", {
|
|
||||||
context: "voucher application"
|
|
||||||
})}
|
|
||||||
<Typography variant="caption">
|
|
||||||
{i18n.t(
|
|
||||||
"If this option is disabled, discount will be counted for every eligible product"
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
onChange={onChange}
|
|
||||||
name={"applyOncePerOrder" as keyof FormData}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</CardContent>
|
|
||||||
<Hr />
|
|
||||||
<CardContent>
|
|
||||||
<Typography variant="subtitle1">{i18n.t("Time Frame")}</Typography>
|
|
||||||
<FormSpacer />
|
|
||||||
<div className={classes.root}>
|
|
||||||
<TextField
|
|
||||||
disabled={disabled}
|
|
||||||
error={!!errors.startDate}
|
|
||||||
helperText={errors.startDate}
|
|
||||||
name={"startDate" as keyof FormData}
|
|
||||||
onChange={onChange}
|
|
||||||
label={i18n.t("Start Date")}
|
|
||||||
value={data.startDate}
|
|
||||||
type="date"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
disabled={disabled}
|
|
||||||
error={!!errors.endDate}
|
|
||||||
helperText={errors.endDate}
|
|
||||||
name={"endDate" as keyof FormData}
|
|
||||||
onChange={onChange}
|
|
||||||
label={i18n.t("End Date")}
|
|
||||||
value={data.endDate}
|
|
||||||
type="date"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
export default VoucherOptions;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default } from "./VoucherOptions";
|
|
||||||
export * from "./VoucherOptions";
|
|
|
@ -1,68 +0,0 @@
|
||||||
import Button from "@material-ui/core/Button";
|
|
||||||
import Card from "@material-ui/core/Card";
|
|
||||||
import AddIcon from "@material-ui/icons/Add";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import Container from "@saleor/components/Container";
|
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
|
||||||
import ProductList from "@saleor/components/ProductList";
|
|
||||||
import { CategoryDetails_category_products_edges_node } from "../../../categories/types/CategoryDetails";
|
|
||||||
import i18n from "../../../i18n";
|
|
||||||
import { FilterPageProps, ListActions, PageListProps } from "../../../types";
|
|
||||||
import { ProductListUrlFilters } from "../../urls";
|
|
||||||
import ProductListFilter from "../ProductListFilter";
|
|
||||||
|
|
||||||
export interface ProductListCardProps
|
|
||||||
extends PageListProps,
|
|
||||||
ListActions,
|
|
||||||
FilterPageProps<ProductListUrlFilters> {
|
|
||||||
currencySymbol: string;
|
|
||||||
products: CategoryDetails_category_products_edges_node[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProductListCard: React.StatelessComponent<
|
|
||||||
ProductListCardProps
|
|
||||||
> = ({
|
|
||||||
currencySymbol,
|
|
||||||
currentTab,
|
|
||||||
filtersList,
|
|
||||||
filterTabs,
|
|
||||||
initialSearch,
|
|
||||||
onAdd,
|
|
||||||
onAll,
|
|
||||||
onSearchChange,
|
|
||||||
onFilterAdd,
|
|
||||||
onFilterSave,
|
|
||||||
onTabChange,
|
|
||||||
onFilterDelete,
|
|
||||||
...listProps
|
|
||||||
}) => (
|
|
||||||
<Container>
|
|
||||||
<PageHeader title={i18n.t("Products")}>
|
|
||||||
<Button onClick={onAdd} color="primary" variant="contained">
|
|
||||||
{i18n.t("Add product")} <AddIcon />
|
|
||||||
</Button>
|
|
||||||
</PageHeader>
|
|
||||||
<Card>
|
|
||||||
<ProductListFilter
|
|
||||||
allTabLabel={i18n.t("All Products")}
|
|
||||||
currencySymbol={currencySymbol}
|
|
||||||
currentTab={currentTab}
|
|
||||||
filterLabel={i18n.t("Select all products where:")}
|
|
||||||
filterTabs={filterTabs}
|
|
||||||
filtersList={filtersList}
|
|
||||||
initialSearch={initialSearch}
|
|
||||||
searchPlaceholder={i18n.t("Search Products...")}
|
|
||||||
onAll={onAll}
|
|
||||||
onSearchChange={onSearchChange}
|
|
||||||
onFilterAdd={onFilterAdd}
|
|
||||||
onFilterSave={onFilterSave}
|
|
||||||
onTabChange={onTabChange}
|
|
||||||
onFilterDelete={onFilterDelete}
|
|
||||||
/>
|
|
||||||
<ProductList {...listProps} />
|
|
||||||
</Card>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
ProductListCard.displayName = "ProductListCard";
|
|
||||||
export default ProductListCard;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default } from "./ProductListCard";
|
|
||||||
export * from "./ProductListCard";
|
|
|
@ -1,366 +0,0 @@
|
||||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
|
||||||
import IconButton from "@material-ui/core/IconButton";
|
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
|
||||||
import React from "react";
|
|
||||||
import { arrayMove } from "react-sortable-hoc";
|
|
||||||
|
|
||||||
import placeholderImg from "@assets/images/placeholder255x255.png";
|
|
||||||
import ActionDialog from "@saleor/components/ActionDialog";
|
|
||||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
|
||||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
|
||||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../config";
|
|
||||||
import SearchCategories from "../../containers/SearchCategories";
|
|
||||||
import SearchCollections from "../../containers/SearchCollections";
|
|
||||||
import i18n from "../../i18n";
|
|
||||||
import { decimal, getMutationState, maybe } from "../../misc";
|
|
||||||
import { productTypeUrl } from "../../productTypes/urls";
|
|
||||||
import ProductUpdatePage, { FormData } from "../components/ProductUpdatePage";
|
|
||||||
import ProductUpdateOperations from "../containers/ProductUpdateOperations";
|
|
||||||
import { TypedProductDetailsQuery } from "../queries";
|
|
||||||
import {
|
|
||||||
ProductImageCreate,
|
|
||||||
ProductImageCreateVariables
|
|
||||||
} from "../types/ProductImageCreate";
|
|
||||||
import { ProductVariantBulkDelete } from "../types/ProductVariantBulkDelete";
|
|
||||||
import {
|
|
||||||
productImageUrl,
|
|
||||||
productListUrl,
|
|
||||||
productUrl,
|
|
||||||
ProductUrlQueryParams,
|
|
||||||
productVariantAddUrl,
|
|
||||||
productVariantEditUrl
|
|
||||||
} from "../urls";
|
|
||||||
|
|
||||||
interface ProductUpdateProps {
|
|
||||||
id: string;
|
|
||||||
params: ProductUrlQueryParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProductUpdate: React.StatelessComponent<ProductUpdateProps> = ({
|
|
||||||
id,
|
|
||||||
params
|
|
||||||
}) => {
|
|
||||||
const navigate = useNavigator();
|
|
||||||
const notify = useNotifier();
|
|
||||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
|
||||||
params.ids
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SearchCategories variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
|
||||||
{({ search: searchCategories, result: searchCategoriesOpts }) => (
|
|
||||||
<SearchCollections variables={DEFAULT_INITIAL_SEARCH_DATA}>
|
|
||||||
{({ search: searchCollections, result: searchCollectionsOpts }) => (
|
|
||||||
<TypedProductDetailsQuery
|
|
||||||
displayLoader
|
|
||||||
require={["product"]}
|
|
||||||
variables={{ id }}
|
|
||||||
>
|
|
||||||
{({ data, loading, refetch }) => {
|
|
||||||
const handleDelete = () => {
|
|
||||||
notify({ text: i18n.t("Product removed") });
|
|
||||||
navigate(productListUrl());
|
|
||||||
};
|
|
||||||
const handleUpdate = () =>
|
|
||||||
notify({ text: i18n.t("Saved changes") });
|
|
||||||
const handleImageCreate = (data: ProductImageCreate) => {
|
|
||||||
const imageError = data.productImageCreate.errors.find(
|
|
||||||
error =>
|
|
||||||
error.field ===
|
|
||||||
("image" as keyof ProductImageCreateVariables)
|
|
||||||
);
|
|
||||||
if (imageError) {
|
|
||||||
notify({
|
|
||||||
text: imageError.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleImageDeleteSuccess = () =>
|
|
||||||
notify({
|
|
||||||
text: i18n.t("Image successfully deleted")
|
|
||||||
});
|
|
||||||
const handleVariantAdd = () =>
|
|
||||||
navigate(productVariantAddUrl(id));
|
|
||||||
|
|
||||||
const handleBulkProductVariantDelete = (
|
|
||||||
data: ProductVariantBulkDelete
|
|
||||||
) => {
|
|
||||||
if (data.productVariantBulkDelete.errors.length === 0) {
|
|
||||||
navigate(productUrl(id), true);
|
|
||||||
reset();
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const product = data ? data.product : undefined;
|
|
||||||
return (
|
|
||||||
<ProductUpdateOperations
|
|
||||||
product={product}
|
|
||||||
onBulkProductVariantDelete={handleBulkProductVariantDelete}
|
|
||||||
onDelete={handleDelete}
|
|
||||||
onImageCreate={handleImageCreate}
|
|
||||||
onImageDelete={handleImageDeleteSuccess}
|
|
||||||
onUpdate={handleUpdate}
|
|
||||||
>
|
|
||||||
{({
|
|
||||||
bulkProductVariantDelete,
|
|
||||||
createProductImage,
|
|
||||||
deleteProduct,
|
|
||||||
deleteProductImage,
|
|
||||||
reorderProductImages,
|
|
||||||
updateProduct,
|
|
||||||
updateSimpleProduct
|
|
||||||
}) => {
|
|
||||||
const handleImageDelete = (id: string) => () =>
|
|
||||||
deleteProductImage.mutate({ id });
|
|
||||||
const handleImageEdit = (imageId: string) => () =>
|
|
||||||
navigate(productImageUrl(id, imageId));
|
|
||||||
const handleSubmit = (data: FormData) => {
|
|
||||||
if (product) {
|
|
||||||
if (product.productType.hasVariants) {
|
|
||||||
updateProduct.mutate({
|
|
||||||
attributes: data.attributes,
|
|
||||||
basePrice: decimal(data.basePrice),
|
|
||||||
category: data.category.value,
|
|
||||||
chargeTaxes: data.chargeTaxes,
|
|
||||||
collections: data.collections.map(
|
|
||||||
collection => collection.value
|
|
||||||
),
|
|
||||||
descriptionJson: JSON.stringify(data.description),
|
|
||||||
id: product.id,
|
|
||||||
isPublished: data.isPublished,
|
|
||||||
name: data.name,
|
|
||||||
publicationDate:
|
|
||||||
data.publicationDate !== ""
|
|
||||||
? data.publicationDate
|
|
||||||
: null
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
updateSimpleProduct.mutate({
|
|
||||||
attributes: data.attributes,
|
|
||||||
basePrice: decimal(data.basePrice),
|
|
||||||
category: data.category.value,
|
|
||||||
chargeTaxes: data.chargeTaxes,
|
|
||||||
collections: data.collections.map(
|
|
||||||
collection => collection.value
|
|
||||||
),
|
|
||||||
descriptionJson: JSON.stringify(data.description),
|
|
||||||
id: product.id,
|
|
||||||
isPublished: data.isPublished,
|
|
||||||
name: data.name,
|
|
||||||
productVariantId: product.variants[0].id,
|
|
||||||
productVariantInput: {
|
|
||||||
quantity: data.stockQuantity,
|
|
||||||
sku: data.sku
|
|
||||||
},
|
|
||||||
publicationDate:
|
|
||||||
data.publicationDate !== ""
|
|
||||||
? data.publicationDate
|
|
||||||
: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const disableFormSave =
|
|
||||||
createProductImage.opts.loading ||
|
|
||||||
deleteProduct.opts.loading ||
|
|
||||||
reorderProductImages.opts.loading ||
|
|
||||||
updateProduct.opts.loading ||
|
|
||||||
loading;
|
|
||||||
const formTransitionState = getMutationState(
|
|
||||||
updateProduct.opts.called ||
|
|
||||||
updateSimpleProduct.opts.called,
|
|
||||||
updateProduct.opts.loading ||
|
|
||||||
updateSimpleProduct.opts.loading,
|
|
||||||
maybe(
|
|
||||||
() => updateProduct.opts.data.productUpdate.errors
|
|
||||||
),
|
|
||||||
maybe(
|
|
||||||
() =>
|
|
||||||
updateSimpleProduct.opts.data.productUpdate.errors
|
|
||||||
),
|
|
||||||
maybe(
|
|
||||||
() =>
|
|
||||||
updateSimpleProduct.opts.data.productVariantUpdate
|
|
||||||
.errors
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const deleteTransitionState = getMutationState(
|
|
||||||
deleteProduct.opts.called,
|
|
||||||
deleteProduct.opts.loading,
|
|
||||||
maybe(
|
|
||||||
() => deleteProduct.opts.data.productDelete.errors
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const bulkProductVariantDeleteTransitionState = getMutationState(
|
|
||||||
bulkProductVariantDelete.opts.called,
|
|
||||||
bulkProductVariantDelete.opts.loading,
|
|
||||||
maybe(
|
|
||||||
() =>
|
|
||||||
bulkProductVariantDelete.opts.data
|
|
||||||
.productVariantBulkDelete.errors
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<WindowTitle title={maybe(() => data.product.name)} />
|
|
||||||
<ProductUpdatePage
|
|
||||||
categories={maybe(
|
|
||||||
() => searchCategoriesOpts.data.categories.edges,
|
|
||||||
[]
|
|
||||||
).map(edge => edge.node)}
|
|
||||||
collections={maybe(
|
|
||||||
() =>
|
|
||||||
searchCollectionsOpts.data.collections.edges,
|
|
||||||
[]
|
|
||||||
).map(edge => edge.node)}
|
|
||||||
disabled={disableFormSave}
|
|
||||||
errors={maybe(
|
|
||||||
() =>
|
|
||||||
updateProduct.opts.data.productUpdate.errors,
|
|
||||||
[]
|
|
||||||
)}
|
|
||||||
fetchCategories={searchCategories}
|
|
||||||
fetchCollections={searchCollections}
|
|
||||||
saveButtonBarState={formTransitionState}
|
|
||||||
images={maybe(() => data.product.images)}
|
|
||||||
header={maybe(() => product.name)}
|
|
||||||
placeholderImage={placeholderImg}
|
|
||||||
product={product}
|
|
||||||
productCollections={maybe(
|
|
||||||
() => product.collections
|
|
||||||
)}
|
|
||||||
variants={maybe(() => product.variants)}
|
|
||||||
onAttributesEdit={() =>
|
|
||||||
navigate(
|
|
||||||
productTypeUrl(data.product.productType.id)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onBack={() => {
|
|
||||||
navigate(productListUrl());
|
|
||||||
}}
|
|
||||||
onDelete={() =>
|
|
||||||
navigate(
|
|
||||||
productUrl(id, {
|
|
||||||
action: "remove"
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onProductShow={() => {
|
|
||||||
if (product) {
|
|
||||||
window.open(product.url);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onImageReorder={({ newIndex, oldIndex }) => {
|
|
||||||
if (product) {
|
|
||||||
let ids = product.images.map(image => image.id);
|
|
||||||
ids = arrayMove(ids, oldIndex, newIndex);
|
|
||||||
reorderProductImages.mutate({
|
|
||||||
imagesIds: ids,
|
|
||||||
productId: product.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
onVariantAdd={handleVariantAdd}
|
|
||||||
onVariantShow={variantId => () =>
|
|
||||||
navigate(
|
|
||||||
productVariantEditUrl(product.id, variantId)
|
|
||||||
)}
|
|
||||||
onImageUpload={file => {
|
|
||||||
if (product) {
|
|
||||||
createProductImage.mutate({
|
|
||||||
alt: "",
|
|
||||||
image: file,
|
|
||||||
product: product.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onImageEdit={handleImageEdit}
|
|
||||||
onImageDelete={handleImageDelete}
|
|
||||||
toolbar={
|
|
||||||
<IconButton
|
|
||||||
color="primary"
|
|
||||||
onClick={() =>
|
|
||||||
navigate(
|
|
||||||
productUrl(id, {
|
|
||||||
action: "remove-variants",
|
|
||||||
ids: listElements
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
isChecked={isSelected}
|
|
||||||
selected={listElements.length}
|
|
||||||
toggle={toggle}
|
|
||||||
toggleAll={toggleAll}
|
|
||||||
/>
|
|
||||||
<ActionDialog
|
|
||||||
open={params.action === "remove"}
|
|
||||||
onClose={() => navigate(productUrl(id), true)}
|
|
||||||
confirmButtonState={deleteTransitionState}
|
|
||||||
onConfirm={() => deleteProduct.mutate({ id })}
|
|
||||||
variant="delete"
|
|
||||||
title={i18n.t("Remove product")}
|
|
||||||
>
|
|
||||||
<DialogContentText
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: i18n.t(
|
|
||||||
"Are you sure you want to remove <strong>{{ name }}</strong>?",
|
|
||||||
{
|
|
||||||
name: product ? product.name : undefined
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ActionDialog>
|
|
||||||
<ActionDialog
|
|
||||||
open={params.action === "remove-variants"}
|
|
||||||
onClose={() => navigate(productUrl(id), true)}
|
|
||||||
confirmButtonState={
|
|
||||||
bulkProductVariantDeleteTransitionState
|
|
||||||
}
|
|
||||||
onConfirm={() =>
|
|
||||||
bulkProductVariantDelete.mutate({
|
|
||||||
ids: params.ids
|
|
||||||
})
|
|
||||||
}
|
|
||||||
variant="delete"
|
|
||||||
title={i18n.t("Remove product variants")}
|
|
||||||
>
|
|
||||||
<DialogContentText
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: i18n.t(
|
|
||||||
"Are you sure you want to remove <strong>{{ number }}</strong> variants?",
|
|
||||||
{
|
|
||||||
number: maybe(
|
|
||||||
() => params.ids.length.toString(),
|
|
||||||
"..."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ActionDialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</ProductUpdateOperations>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</TypedProductDetailsQuery>
|
|
||||||
)}
|
|
||||||
</SearchCollections>
|
|
||||||
)}
|
|
||||||
</SearchCategories>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default ProductUpdate;
|
|
|
@ -3,6 +3,7 @@ import IconButton from "@material-ui/core/IconButton";
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import placeholderImg from "@assets/images/placeholder255x255.png";
|
||||||
import ActionDialog from "@saleor/components/ActionDialog";
|
import ActionDialog from "@saleor/components/ActionDialog";
|
||||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||||
|
@ -32,7 +33,6 @@ import {
|
||||||
productVariantAddUrl,
|
productVariantAddUrl,
|
||||||
productVariantEditUrl
|
productVariantEditUrl
|
||||||
} from "../../urls";
|
} from "../../urls";
|
||||||
import placeholderImg from "../@assets/images/placeholder255x255.png";
|
|
||||||
import {
|
import {
|
||||||
createImageReorderHandler,
|
createImageReorderHandler,
|
||||||
createImageUploadHandler,
|
createImageUploadHandler,
|
||||||
|
|
|
@ -11,13 +11,12 @@ import Typography from "@material-ui/core/Typography";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import SVG from "react-inlinesvg";
|
import SVG from "react-inlinesvg";
|
||||||
|
|
||||||
|
import photoIcon from "@assets/images/photo-icon.svg";
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
import CardTitle from "@saleor/components/CardTitle";
|
||||||
import i18n from "../../../i18n";
|
import i18n from "../../../i18n";
|
||||||
import { getUserInitials, maybe } from "../../../misc";
|
import { getUserInitials, maybe } from "../../../misc";
|
||||||
import { StaffMemberDetails_user } from "../../types/StaffMemberDetails";
|
import { StaffMemberDetails_user } from "../../types/StaffMemberDetails";
|
||||||
|
|
||||||
import photoIcon from "../@assets/images/photo-icon.svg";
|
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
avatar: {
|
avatar: {
|
||||||
|
|
|
@ -27,7 +27,7 @@ jest.mock("draft-js/lib/generateRandomKey");
|
||||||
(generateRandomKey as any).mockImplementation(() => "testKey");
|
(generateRandomKey as any).mockImplementation(() => "testKey");
|
||||||
|
|
||||||
initStoryshots({
|
initStoryshots({
|
||||||
configPath: "saleor/static/dashboard-next/storybook/",
|
configPath: "src/storybook/",
|
||||||
test({ story }) {
|
test({ story }) {
|
||||||
const result = render(story.render() as any);
|
const result = render(story.render() as any);
|
||||||
expect(toJSON(result)).toMatchSnapshot();
|
expect(toJSON(result)).toMatchSnapshot();
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
import { storiesOf } from "@storybook/react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import placeholderImage from "@assets/images/placeholder255x255.png";
|
|
||||||
import { category as categoryFixture } from "../../../categories/fixtures";
|
|
||||||
import {
|
|
||||||
filterPageProps,
|
|
||||||
filters,
|
|
||||||
listActionsProps,
|
|
||||||
pageListProps
|
|
||||||
} from "../../../fixtures";
|
|
||||||
import ProductListCard, {
|
|
||||||
ProductListCardProps
|
|
||||||
} from "../../../products/components/ProductListCard";
|
|
||||||
import Decorator from "../../Decorator";
|
|
||||||
|
|
||||||
const products = categoryFixture(placeholderImage).products.edges.map(
|
|
||||||
edge => edge.node
|
|
||||||
);
|
|
||||||
|
|
||||||
const props: ProductListCardProps = {
|
|
||||||
...listActionsProps,
|
|
||||||
...pageListProps.default,
|
|
||||||
...filterPageProps,
|
|
||||||
products
|
|
||||||
};
|
|
||||||
|
|
||||||
storiesOf("Views / Products / Product list", module)
|
|
||||||
.addDecorator(Decorator)
|
|
||||||
.add("default", () => <ProductListCard {...props} />)
|
|
||||||
.add("with custom filters", () => (
|
|
||||||
<ProductListCard {...props} filtersList={filters} />
|
|
||||||
))
|
|
||||||
.add("loading", () => (
|
|
||||||
<ProductListCard
|
|
||||||
{...props}
|
|
||||||
products={undefined}
|
|
||||||
filtersList={undefined}
|
|
||||||
currentTab={undefined}
|
|
||||||
disabled={true}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
.add("no data", () => <ProductListCard {...props} products={[]} />);
|
|
Loading…
Reference in a new issue