Repository: saleor/saleor
Pull request: Fix minor bugs
I want to merge this change because it fixes various bugs in dashboard.
Fixes #2722 
Fixes #2736 
Fixes #2737 
Fixes #2756 

### Pull Request Checklist

<!-- Please keep this section. It will make maintainer's life easier. -->

1. [ ] Privileged views and APIs are guarded by proper permission checks.
1. [ ] All visible strings are translated with proper context.
1. [ ] All data-formatting is locale-aware (dates, numbers, and so on).
1. [ ] Database queries are optimized and the number of queries is constant.
1. [ ] The changes are tested.
1. [ ] The code is documented (docstrings, project documentation).


Problem statement:
Deleting a product image doesn't refresh the UI
### Steps to reproduce the problem
1. Go to the product details page.
2. Delete a product image.

### What I expected to happen
UI should be refreshed. Instead, a success message is displayed but the image remains on the list.


Deleting a product redirects to category page
In the new dashboard, removing a product redirects to the category details page. Ideally, we should redirect back to the section where the user came from, but it's separate and more complex issue. Here, I would expect to be redirected back to the product list as it feels more like the "products" context than a category.

### Steps to reproduce the problem
1. Go to the product details page.
2. Remove the product.


"Cancel" buttons doesn't work
In the new dashboard:

### Steps to reproduce the problem
1. Go to the product details page.
2. Click the "cancel" button.

### What I expected to happen
Redirect back to the product list.



Dashboard 2.0: Clicking a link should scroll the page to top
### Steps to reproduce the problem
1. Visit a page with a long list of items
2. Scroll to the bottom
3. Click the last item
4. Observe how you now see the bottom of the details page as the scroll position is maintained

### What I expected to happen
See the header of the next page

Hints from issue discussion:
Also, I've noticed that after I manually navigate back to the product list, the product that I just deleted stays on the list until the "product list" GraphQL call returns - shouldn't the cache be updated when the successful response from the "delete" mutations comes?

Commit message: Refresh UI after image removal

Patch to review:
diff --git a/saleor/static/dashboard-next/components/SaveButtonBar/SaveButtonBar.tsx b/saleor/static/dashboard-next/components/SaveButtonBar/SaveButtonBar.tsx
index 35f6b01da94..5dc15cd8004 100644
--- a/saleor/static/dashboard-next/components/SaveButtonBar/SaveButtonBar.tsx
+++ b/saleor/static/dashboard-next/components/SaveButtonBar/SaveButtonBar.tsx
@@ -20,7 +20,7 @@ interface SaveButtonBarProps {
     delete?: string;
     save?: string;
   };
-  onCancel?: () => void;
+  onCancel: () => void;
   onDelete?: () => void;
   onSave(event: any);
 }
diff --git a/saleor/static/dashboard-next/components/ScrollToTop.tsx b/saleor/static/dashboard-next/components/ScrollToTop.tsx
new file mode 100644
index 00000000000..13ff1d84998
--- /dev/null
+++ b/saleor/static/dashboard-next/components/ScrollToTop.tsx
@@ -0,0 +1,16 @@
+import * as React from "react";
+import { RouteComponentProps, withRouter } from "react-router-dom";
+
+class ScrollToTop extends React.Component<RouteComponentProps<{}>> {
+  componentDidUpdate(prevProps) {
+    if (this.props.location !== prevProps.location) {
+      window.scrollTo(0, 0);
+    }
+  }
+
+  render() {
+    return this.props.children;
+  }
+}
+
+export default withRouter(ScrollToTop);
diff --git a/saleor/static/dashboard-next/customers/components/CustomerEditPage/CustomerEditPage.tsx b/saleor/static/dashboard-next/customers/components/CustomerEditPage/CustomerEditPage.tsx
index 3462eb5e69c..09554ec64f2 100644
--- a/saleor/static/dashboard-next/customers/components/CustomerEditPage/CustomerEditPage.tsx
+++ b/saleor/static/dashboard-next/customers/components/CustomerEditPage/CustomerEditPage.tsx
@@ -98,6 +98,7 @@ const CustomerEditPage: React.StatelessComponent<CustomerEditPageProps> = ({
           <SaveButtonBar
             disabled={disabled || !onSubmit || !hasChanged}
             state={saveButtonBarState}
+            onCancel={onBack}
             onSave={onSubmit}
           />
         </Container>
diff --git a/saleor/static/dashboard-next/index.tsx b/saleor/static/dashboard-next/index.tsx
index 3c375a73ac3..d67cbd98e4d 100644
--- a/saleor/static/dashboard-next/index.tsx
+++ b/saleor/static/dashboard-next/index.tsx
@@ -22,6 +22,7 @@ import "./i18n";
 import PageSection from "./pages";
 import ProductSection from "./products";
 import theme from "./theme";
+import ScrollToTop from "./components/ScrollToTop";
 
 const cookies = new Cookies();
 
@@ -74,11 +75,16 @@ render(
                 {({ isAuthenticated }) =>
                   isAuthenticated ? (
                     <AppRoot>
-                      <Switch>
-                        <Route path="/categories" component={CategorySection} />
-                        <Route path="/pages" component={PageSection} />
-                        <Route path="/products" component={ProductSection} />
-                      </Switch>
+                      <ScrollToTop>
+                        <Switch>
+                          <Route
+                            path="/categories"
+                            component={CategorySection}
+                          />
+                          <Route path="/pages" component={PageSection} />
+                          <Route path="/products" component={ProductSection} />
+                        </Switch>
+                      </ScrollToTop>
                     </AppRoot>
                   ) : (
                     <Route component={Auth} />
diff --git a/saleor/static/dashboard-next/products/components/ProductImagePage/ProductImagePage.tsx b/saleor/static/dashboard-next/products/components/ProductImagePage/ProductImagePage.tsx
index 805297f8689..67d09910d27 100644
--- a/saleor/static/dashboard-next/products/components/ProductImagePage/ProductImagePage.tsx
+++ b/saleor/static/dashboard-next/products/components/ProductImagePage/ProductImagePage.tsx
@@ -116,6 +116,7 @@ const ProductImagePage = decorate<ProductImagePageProps>(
             <SaveButtonBar
               disabled={disabled || !onSubmit || !hasChanged}
               state={saveButtonBarState}
+              onCancel={onBack}
               onDelete={onDelete}
               onSave={submit}
             />
diff --git a/saleor/static/dashboard-next/products/components/ProductUpdatePage/ProductUpdatePage.tsx b/saleor/static/dashboard-next/products/components/ProductUpdatePage/ProductUpdatePage.tsx
index 306d016182a..ea72c78f376 100644
--- a/saleor/static/dashboard-next/products/components/ProductUpdatePage/ProductUpdatePage.tsx
+++ b/saleor/static/dashboard-next/products/components/ProductUpdatePage/ProductUpdatePage.tsx
@@ -331,6 +331,7 @@ export const ProductUpdate = decorate<ProductUpdateProps>(
                     </div>
                   </div>
                   <SaveButtonBar
+                    onCancel={onBack}
                     onDelete={toggleDeleteDialog}
                     onSave={submit}
                     state={saveButtonBarState}
diff --git a/saleor/static/dashboard-next/products/containers/ProductUpdateOperations.tsx b/saleor/static/dashboard-next/products/containers/ProductUpdateOperations.tsx
index 905e6903147..f22068ea3d7 100644
--- a/saleor/static/dashboard-next/products/containers/ProductUpdateOperations.tsx
+++ b/saleor/static/dashboard-next/products/containers/ProductUpdateOperations.tsx
@@ -96,21 +96,7 @@ const ProductImageDeleteProvider: React.StatelessComponent<
         data,
         error,
         loading,
-        mutate: opts => {
-          const optimisticResponse = {
-            productImageDelete: {
-              __typename: "ProductImageDelete",
-              productImage: {
-                __typename: "ProductImage",
-                id: opts.variables.id
-              }
-            }
-          };
-          return mutate({
-            optimisticResponse,
-            variables: opts.variables
-          });
-        }
+        mutate
       })
     }
   </TypedProductImageDeleteMutation>
diff --git a/saleor/static/dashboard-next/products/mutations.ts b/saleor/static/dashboard-next/products/mutations.ts
index bc9d71d0d36..ff9982627a9 100644
--- a/saleor/static/dashboard-next/products/mutations.ts
+++ b/saleor/static/dashboard-next/products/mutations.ts
@@ -28,10 +28,7 @@ import {
 } from "../gql-types";
 import { TypedMutation } from "../mutations";
 
-import {
-  fragmentProduct,
-  fragmentVariant
-} from "./queries";
+import { fragmentProduct, fragmentVariant } from "./queries";
 
 export const productImageCreateMutation = gql`
   ${fragmentProduct}
@@ -273,6 +270,7 @@ export const productImageDeleteMutation = gql`
   mutation ProductImageDelete($id: ID!) {
     productImageDelete(id: $id) {
       product {
+        id
         images {
           edges {
             node {
diff --git a/saleor/static/dashboard-next/products/views/ProductUpdate.tsx b/saleor/static/dashboard-next/products/views/ProductUpdate.tsx
index e768b2fc2b7..3210ae03fe8 100644
--- a/saleor/static/dashboard-next/products/views/ProductUpdate.tsx
+++ b/saleor/static/dashboard-next/products/views/ProductUpdate.tsx
@@ -3,7 +3,6 @@ import { arrayMove } from "react-sortable-hoc";
 
 import * as placeholderImg from "../../../images/placeholder255x255.png";
 import { attributesListUrl } from "../../attributes";
-import { categoryShowUrl } from "../../categories";
 import ErrorMessageCard from "../../components/ErrorMessageCard";
 import Messages from "../../components/messages";
 import Navigator from "../../components/Navigator";
@@ -44,7 +43,7 @@ export const ProductUpdate: React.StatelessComponent<ProductUpdateProps> = ({
 
                   const handleDelete = () => {
                     pushMessage({ text: i18n.t("Product removed") });
-                    navigate(categoryShowUrl(data.product.category.id));
+                    navigate(productListUrl);
                   };
                   const handleUpdate = () =>
                     pushMessage({ text: i18n.t("Saved changes") });
diff --git a/saleor/static/dashboard-next/storybook/stories/components/SaveButtonBar.tsx b/saleor/static/dashboard-next/storybook/stories/components/SaveButtonBar.tsx
index fceed1e57f8..3f24f55a0cb 100644
--- a/saleor/static/dashboard-next/storybook/stories/components/SaveButtonBar.tsx
+++ b/saleor/static/dashboard-next/storybook/stories/components/SaveButtonBar.tsx
@@ -50,6 +50,7 @@ class InteractiveDemo extends React.Component<
   render() {
     return (
       <SaveButtonBar
+        onCancel={() => undefined}
         onSave={this.handleButtonClick}
         state={this.state.state as any}
       />


Code review comments:
diff_hunk: @@ -0,0 +1,16 @@
+import * as React from "react";
+import { RouteComponentProps, withRouter } from "react-router-dom";
+
+class ScrollToTop extends React.Component<RouteComponentProps<{}>> {
path: saleor/static/dashboard-next/components/ScrollToTop.tsx
text: @user1:
Why not have `<Navigator>` scroll in the function it exposes (and also have it store the current scroll position in the routing state)?