Skip to content

Commit

Permalink
Introduce intial component for catalog discounts (#4414)
Browse files Browse the repository at this point in the history
Co-authored-by: Patryk Andrzejewski <[email protected]>
  • Loading branch information
poulch and andrzejewsky authored Nov 13, 2023
1 parent c4b6085 commit 061dab8
Show file tree
Hide file tree
Showing 30 changed files with 625 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/empty-falcons-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"saleor-dashboard": minor
---

Introduce intial component for catalog discounts
24 changes: 24 additions & 0 deletions locale/defaultMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2606,6 +2606,9 @@
"context": "webhooks inactive label",
"string": "Inactive"
},
"GOdq5V": {
"string": "Catalog"
},
"GTCg9O": {
"string": "You must add at least one voucher code"
},
Expand Down Expand Up @@ -3933,6 +3936,9 @@
"context": "page header",
"string": "Create Voucher"
},
"PuQb0P": {
"string": "Reward"
},
"Pyjarj": {
"string": "This shipping rate has no postal codes assigned"
},
Expand Down Expand Up @@ -4282,6 +4288,9 @@
"context": "unassign attribute from product type, button",
"string": "Unassign"
},
"S8kqP9": {
"string": "Conditions"
},
"SBb6Ej": {
"context": "select a warehouse to fulfill product from",
"string": "Select warehouse..."
Expand Down Expand Up @@ -5152,6 +5161,9 @@
"Xtd0AT": {
"string": "Original String"
},
"XtlUj6": {
"string": "Add your first rule to set up a promotion"
},
"Xu4ech": {
"context": "deactivate app",
"string": "Are you sure you want to disable this app? Your data will be kept until you reactivate the app."
Expand Down Expand Up @@ -6394,6 +6406,9 @@
"context": "PluginChannelConfigurationCell channel title",
"string": "Per channel"
},
"gzM1em": {
"string": "Add rule"
},
"h1rPPg": {
"context": "dialog content",
"string": "Are you sure you want to delete {attributeName}?"
Expand Down Expand Up @@ -6811,6 +6826,9 @@
"context": "total order price",
"string": "Total"
},
"kAAlGL": {
"string": "Rules"
},
"kAPaN6": {
"context": "empty metadata text",
"string": "Empty"
Expand Down Expand Up @@ -6859,6 +6877,9 @@
"kN6SLs": {
"string": "Min Value"
},
"kNK4es": {
"string": "Discount value"
},
"kPIZ65": {
"context": "page header",
"string": "Order #{orderNumber}"
Expand Down Expand Up @@ -8243,6 +8264,9 @@
"context": "order history message",
"string": "Order was confirmed"
},
"ucLtY8": {
"string": "Discount rules for products, collections or categories."
},
"uccjUM": {
"context": "Dry run objects",
"string": "Objects"
Expand Down
2 changes: 1 addition & 1 deletion src/components/RichTextEditor/RichTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface RichTextEditorProps extends Omit<EditorJsProps, "onChange"> {
name: string;
editorRef:
| React.RefCallback<EditorCore>
| React.MutableRefObject<EditorCore>
| React.MutableRefObject<EditorCore | null>
| null;
// onChange with value shouldn't be used due to issues with React and EditorJS integration
onChange?: () => void;
Expand Down
20 changes: 12 additions & 8 deletions src/components/SubMenu/SubMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { List, Text } from "@saleor/macaw-ui-next";
import { Box, List, Text } from "@saleor/macaw-ui-next";
import React, { ReactNode } from "react";

export interface MenuItem {
id: string;
title: ReactNode;
description: ReactNode;
icon?: ReactNode;
onClick: () => void;
}

Expand All @@ -19,7 +20,7 @@ export const SubMenu = ({ menuItems }: SubMenuProps) => {
__minWidth={326}
borderRadius={3}
>
{menuItems.map(({ id, title, description, onClick }, idx) => {
{menuItems.map(({ id, title, description, icon, onClick }, idx) => {
const isLastItem = idx === menuItems.length - 1;

return (
Expand All @@ -33,12 +34,15 @@ export const SubMenu = ({ menuItems }: SubMenuProps) => {
borderBottomWidth={1}
borderColor="neutralPlain"
>
<Text
data-test-id={String(title).toLowerCase()}
variant="bodyStrong"
>
{title}
</Text>
<Box display="flex" gap={3} alignItems="center">
{icon}
<Text
data-test-id={String(title).toLowerCase()}
variant="bodyStrong"
>
{title}
</Text>
</Box>
<Text>{description}</Text>
</List.Item>
);
Expand Down
65 changes: 65 additions & 0 deletions src/discounts/components/DiscountRules/DiscountRules.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { act, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";

import { DiscountRules } from "./DiscountRules";

jest.mock("react-intl", () => ({
useIntl: jest.fn(() => ({
formatMessage: jest.fn(x => x.defaultMessage),
})),
defineMessages: jest.fn(x => x),
}));

describe("DiscountRules", () => {
it("should render placeholder when no rules", () => {
// Arrange & Act
render(<DiscountRules rules={[]} onRuleAdd={jest.fn()} />);

// Assert
expect(
screen.getByText(/add your first rule to set up a promotion/i),
).toBeInTheDocument();
});

it("should render discount rules", () => {
// Arrange & Act
render(
<DiscountRules
rules={[
{
id: "Catalog rule 2",
name: "Catalog rule 2",
description: "",
},
]}
onRuleAdd={jest.fn()}
/>,
);

// Assert
expect(screen.getByText(/catalog rule/i)).toBeInTheDocument();
});

it("should fire callback when user whant to add new rule", async () => {
// Arrange
const onRuleAdd = jest.fn();
render(<DiscountRules rules={[]} onRuleAdd={onRuleAdd} />);

// Act
await act(() => {
userEvent.click(screen.getByRole("button", { name: /add rule/i }));
});

await waitFor(() => {
expect(screen.getByText(/^catalog$/i)).toBeInTheDocument();
});

await act(async () => {
await userEvent.click(screen.getByText(/^catalog$/i));
});

// Assert
expect(onRuleAdd).toHaveBeenCalled();
});
});
32 changes: 32 additions & 0 deletions src/discounts/components/DiscountRules/DiscountRules.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { DashboardCard } from "@dashboard/components/Card";
import { Box } from "@saleor/macaw-ui-next";
import React from "react";
import { useIntl } from "react-intl";

import { AddButton } from "./componenets/AddButton";
import { RulesList } from "./componenets/RulesList";
import { messages } from "./messages";
import { DiscountRule } from "./types";

interface DiscountRulesProps {
rules: DiscountRule[];
onRuleAdd: () => void;
}

export const DiscountRules = ({ onRuleAdd, rules }: DiscountRulesProps) => {
const intl = useIntl();

return (
<DashboardCard>
<DashboardCard.Title>
<Box display="flex" justifyContent="space-between" alignItems="center">
{intl.formatMessage(messages.title)}
<AddButton onCatalogClick={onRuleAdd} />
</Box>
</DashboardCard.Title>
<DashboardCard.Content>
<RulesList rules={rules} />
</DashboardCard.Content>
</DashboardCard>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { MenuItem, SubMenu } from "@dashboard/components/SubMenu";
import {
ArrowDownIcon,
Box,
Button,
PlusIcon,
Popover,
ProductsIcons,
} from "@saleor/macaw-ui-next";
import React, { useMemo, useState } from "react";
import { useIntl } from "react-intl";

import { messages } from "../../messages";

interface AddButtonProps {
onCatalogClick: () => void;
}

export const AddButton = ({ onCatalogClick }: AddButtonProps) => {
const intl = useIntl();
const [isSubMenuOpen, setSubMenuOpen] = useState(false);

const handleCatalogClick = () => {
onCatalogClick();
setSubMenuOpen(false);
};

const subMenuItems = useMemo<MenuItem[]>(
() => [
{
id: "catalog",
title: intl.formatMessage(messages.catalog),
description: intl.formatMessage(messages.catalogDescription),
icon: <ProductsIcons />,
onClick: handleCatalogClick,
},
],
[],
);

return (
<Popover open={isSubMenuOpen} onOpenChange={setSubMenuOpen}>
<Popover.Trigger>
<Button
type="button"
backgroundColor="interactiveNeutralDefault"
color="textNeutralContrasted"
>
<PlusIcon />
{intl.formatMessage(messages.addRule)}
<ArrowDownIcon />
</Button>
</Popover.Trigger>
<Popover.Content align="end">
<Box marginTop={1}>
<SubMenu menuItems={subMenuItems} />
</Box>
</Popover.Content>
</Popover>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./AddButton";
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Box, Switch, Text } from "@saleor/macaw-ui-next";
import React from "react";

import { DiscountType } from "../../types";

interface DiscountTypeSwitchProps {
selected: DiscountType;
currencySymbol: string;
onChange: (type: string) => void;
}

const PERCENT_SYMBOL = "%";

export const DiscountTypeSwitch = ({
selected,
currencySymbol,
onChange,
}: DiscountTypeSwitchProps) => {
return (
<Switch
defaultValue={selected}
onValueChange={onChange}
height={14}
paddingY={1.5}
__width="fit-content"
>
<Switch.Item id="fixed" value="fixed" marginLeft={0.5}>
<Box
display="flex"
justifyContent="center"
alignItems="center"
width={9}
height="100%"
>
<Text>{currencySymbol}</Text>
</Box>
</Switch.Item>
<Switch.Item id="percentage" value="percentage" marginRight={0.5}>
<Box
display="flex"
justifyContent="center"
alignItems="center"
width={9}
height="100%"
>
<Text>{PERCENT_SYMBOL}</Text>
</Box>
</Switch.Item>
</Switch>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./DiscountTypeSwitch";
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Box, PlusIcon, Text } from "@saleor/macaw-ui-next";
import React from "react";
import { useIntl } from "react-intl";

import { messages } from "../../messages";

export const Placeholder = () => {
const intl = useIntl();

return (
<Box
paddingX={4}
paddingY={12}
display="flex"
flexDirection="column"
alignItems="center"
gap={4}
borderRadius={4}
borderColor="neutralPlain"
borderWidth={1}
borderStyle="solid"
>
<PlusIcon size="large" color="iconNeutralSubdued" />
<Text size="large">{intl.formatMessage(messages.placeholder)}</Text>
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Placeholder";
Loading

0 comments on commit 061dab8

Please sign in to comment.