diff --git a/CHANGELOG.md b/CHANGELOG.md index 202ced51b..77d8f7beb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable, unreleased changes to this project will be documented in this file. - Fix tax settings updating - #243 by @dominik-zeglen - Add secret fields in plugin configuration - #246 by @dominik-zeglen - Fix subcategories pagination - #249 by @dominik-zeglen +- Update customer's details page design - #248 by @dominik-zeglen - Use Apollo Hooks - #254 by @dominik-zeglen ## 2.0.0 diff --git a/_redirects b/_redirects new file mode 100644 index 000000000..c3eb143cd --- /dev/null +++ b/_redirects @@ -0,0 +1,3 @@ +# Redirect rules for Netlify + +/* /index.html 200 diff --git a/locale/messages.pot b/locale/messages.pot index 394dd6744..3ad29f137 100644 --- a/locale/messages.pot +++ b/locale/messages.pot @@ -1,6 +1,6 @@ msgid "" msgstr "" -"POT-Creation-Date: 2019-11-08T10:47:25.155Z\n" +"POT-Creation-Date: 2019-11-13T13:13:30.128Z\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "MIME-Version: 1.0\n" @@ -127,6 +127,14 @@ msgctxt "time during voucher is active, header" msgid "Active Dates" msgstr "" +#: build/locale/src/customers/components/CustomerDetails/CustomerDetails.json +#. [src.customers.components.CustomerDetails.3298169382] - section subheader +#. defaultMessage is: +#. Active member since {date} +msgctxt "section subheader" +msgid "Active member since {date}" +msgstr "" + #: build/locale/src/home/components/HomeActivityCard/HomeActivityCard.json #. [homeActivityCardHeader] - header #. defaultMessage is: @@ -2103,6 +2111,14 @@ msgctxt "subheader" msgid "Contact Information" msgstr "" +#: build/locale/src/customers/components/CustomerInfo/CustomerInfo.json +#. [src.customers.components.CustomerInfo.778526801] - customer contact section, header +#. defaultMessage is: +#. Contact Informations +msgctxt "customer contact section, header" +msgid "Contact Informations" +msgstr "" + #: build/locale/src/pages/components/PageInfo/PageInfo.json #. [src.pages.components.PageInfo.1116746286] - page content #. defaultMessage is: @@ -2795,14 +2811,6 @@ msgctxt "description" msgid "Customer password reset URL" msgstr "" -#: build/locale/src/customers/components/CustomerDetails/CustomerDetails.json -#. [src.customers.components.CustomerDetails.2200102325] - section subheader -#. defaultMessage is: -#. Customer since: {date} -msgctxt "section subheader" -msgid "Customer since: {date}" -msgstr "" - #: build/locale/src/intl.json #. [src.customers] - customers section name #. defaultMessage is: @@ -5943,6 +5951,14 @@ msgctxt "description" msgid "Permissions" msgstr "" +#: build/locale/src/customers/components/CustomerInfo/CustomerInfo.json +#. [src.customers.components.CustomerInfo.252313757] - customer informations, header +#. defaultMessage is: +#. Personal Informations +msgctxt "customer informations, header" +msgid "Personal Informations" +msgstr "" + #: build/locale/src/taxes/components/CountryTaxesPage/CountryTaxesPage.json #. [src.taxes.components.CountryTaxesPage.815189256] - tax rate #. defaultMessage is: diff --git a/src/components/CardTitle/CardTitle.tsx b/src/components/CardTitle/CardTitle.tsx index eb565e834..ec72d2f55 100644 --- a/src/components/CardTitle/CardTitle.tsx +++ b/src/components/CardTitle/CardTitle.tsx @@ -23,6 +23,7 @@ const useStyles = makeStyles(theme => ({ }), title: { flex: 1, + fontWeight: 500, lineHeight: 1 }, toolbar: { diff --git a/src/components/ControlledCheckbox.tsx b/src/components/ControlledCheckbox.tsx index 7ed45d330..4a79d9085 100644 --- a/src/components/ControlledCheckbox.tsx +++ b/src/components/ControlledCheckbox.tsx @@ -4,6 +4,7 @@ import React from "react"; import Checkbox from "./Checkbox"; interface ControlledCheckboxProps { + className?: string; name: string; label?: React.ReactNode; checked: boolean; @@ -16,7 +17,8 @@ export const ControlledCheckbox: React.FC = ({ disabled, name, label, - onChange + onChange, + ...props }) => ( = ({ /> } label={label} + {...props} /> ); ControlledCheckbox.displayName = "ControlledCheckbox"; diff --git a/src/customers/components/CustomerCreatePage/CustomerCreatePage.tsx b/src/customers/components/CustomerCreatePage/CustomerCreatePage.tsx index 1ce4a9397..f5e814572 100644 --- a/src/customers/components/CustomerCreatePage/CustomerCreatePage.tsx +++ b/src/customers/components/CustomerCreatePage/CustomerCreatePage.tsx @@ -9,7 +9,9 @@ import Form from "@saleor/components/Form"; import Grid from "@saleor/components/Grid"; import PageHeader from "@saleor/components/PageHeader"; import SaveButtonBar from "@saleor/components/SaveButtonBar"; +import useAddressValidation from "@saleor/hooks/useAddressValidation"; import { sectionNames } from "@saleor/intl"; +import { AddressInput } from "@saleor/types/globalTypes"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import { UserError } from "../../../types"; import { AddressTypeInput } from "../../types"; @@ -18,14 +20,18 @@ import CustomerCreateAddress from "../CustomerCreateAddress/CustomerCreateAddres import CustomerCreateDetails from "../CustomerCreateDetails"; import CustomerCreateNote from "../CustomerCreateNote/CustomerCreateNote"; -export interface CustomerCreatePageFormData extends AddressTypeInput { +export interface CustomerCreatePageFormData { customerFirstName: string; customerLastName: string; email: string; note: string; } +export interface CustomerCreatePageSubmitData + extends CustomerCreatePageFormData { + address: AddressInput; +} -const initialForm: CustomerCreatePageFormData = { +const initialForm: CustomerCreatePageFormData & AddressTypeInput = { city: "", cityArea: "", companyName: "", @@ -49,7 +55,7 @@ export interface CustomerCreatePageProps { errors: UserError[]; saveButtonBar: ConfirmButtonTransitionState; onBack: () => void; - onSubmit: (data: CustomerCreatePageFormData) => void; + onSubmit: (data: CustomerCreatePageSubmitData) => void; } const CustomerCreatePage: React.FC = ({ @@ -67,12 +73,67 @@ const CustomerCreatePage: React.FC = ({ label: country.country, value: country.code })); + const { + errors: validationErrors, + submit: handleSubmitWithAddress + } = useAddressValidation(formData => + onSubmit({ + address: { + city: formData.city, + cityArea: formData.cityArea, + companyName: formData.companyName, + country: formData.country, + countryArea: formData.countryArea, + firstName: formData.firstName, + lastName: formData.lastName, + phone: formData.phone, + postalCode: formData.postalCode, + streetAddress1: formData.streetAddress1, + streetAddress2: formData.streetAddress2 + }, + customerFirstName: formData.customerFirstName, + customerLastName: formData.customerLastName, + email: formData.email, + note: formData.note + }) + ); + + const handleSubmit = ( + formData: CustomerCreatePageFormData & AddressTypeInput + ) => { + const areAddressInputFieldsModified = ([ + "city", + "companyName", + "country", + "countryArea", + "firstName", + "lastName", + "phone", + "postalCode", + "streetAddress1", + "streetAddress2" + ] as Array) + .map(key => formData[key]) + .some(field => field !== ""); + + if (areAddressInputFieldsModified) { + handleSubmitWithAddress(formData); + } else { + onSubmit({ + address: null, + customerFirstName: formData.customerFirstName, + customerLastName: formData.customerLastName, + email: formData.email, + note: formData.note + }); + } + }; return (
{({ change, data, errors: formErrors, hasChanged, submit }) => { diff --git a/src/customers/components/CustomerDetails/CustomerDetails.tsx b/src/customers/components/CustomerDetails/CustomerDetails.tsx index 5496ed387..1db8cf109 100644 --- a/src/customers/components/CustomerDetails/CustomerDetails.tsx +++ b/src/customers/components/CustomerDetails/CustomerDetails.tsx @@ -9,39 +9,34 @@ import { FormattedMessage, useIntl } from "react-intl"; import CardTitle from "@saleor/components/CardTitle"; import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox"; -import { FormSpacer } from "@saleor/components/FormSpacer"; import Skeleton from "@saleor/components/Skeleton"; -import { commonMessages } from "@saleor/intl"; +import { maybe } from "@saleor/misc"; +import { FormErrors } from "@saleor/types"; import { CustomerDetails_user } from "../../types/CustomerDetails"; const useStyles = makeStyles(theme => ({ cardTitle: { - height: 64 + height: 72 }, - root: { - display: "grid" as "grid", - gridColumnGap: theme.spacing(2), - gridRowGap: theme.spacing(3), - gridTemplateColumns: "1fr 1fr" + checkbox: { + marginBottom: theme.spacing() + }, + content: { + paddingTop: theme.spacing() + }, + subtitle: { + marginTop: theme.spacing() } })); export interface CustomerDetailsProps { customer: CustomerDetails_user; data: { - firstName: string; - lastName: string; - email: string; isActive: boolean; note: string; }; disabled: boolean; - errors: { - firstName?: string; - lastName?: string; - email?: string; - note?: string; - }; + errors: FormErrors<"isActive" | "note">; onChange: (event: React.ChangeEvent) => void; } @@ -57,11 +52,15 @@ const CustomerDetails: React.FC = props => { className={classes.cardTitle} title={ <> - + {maybe(() => customer.email, )} {customer && customer.dateJoined ? ( - + = props => { } /> - + = props => { name="isActive" onChange={onChange} /> - -
- - -
- - - = ({ onChange={change} /> + + customer.orders.edges.map(edge => edge.node) diff --git a/src/customers/components/CustomerInfo/CustomerInfo.tsx b/src/customers/components/CustomerInfo/CustomerInfo.tsx new file mode 100644 index 000000000..26c6893a9 --- /dev/null +++ b/src/customers/components/CustomerInfo/CustomerInfo.tsx @@ -0,0 +1,108 @@ +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import { makeStyles } from "@material-ui/core/styles"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import CardTitle from "@saleor/components/CardTitle"; +import Grid from "@saleor/components/Grid"; +import Hr from "@saleor/components/Hr"; +import { commonMessages } from "@saleor/intl"; + +const useStyles = makeStyles(theme => ({ + content: { + paddingTop: theme.spacing(2) + }, + hr: { + margin: theme.spacing(3, 0) + }, + sectionHeader: { + marginBottom: theme.spacing() + } +})); + +export interface CustomerInfoProps { + data: { + firstName: string; + lastName: string; + email: string; + }; + disabled: boolean; + errors: { + firstName?: string; + lastName?: string; + email?: string; + }; + onChange: (event: React.ChangeEvent) => void; +} + +const CustomerInfo: React.FC = props => { + const { data, disabled, errors, onChange } = props; + const classes = useStyles(props); + + const intl = useIntl(); + + return ( + + + } + /> + + + + + + + + +
+ + + + +
+
+ ); +}; +CustomerInfo.displayName = "CustomerInfo"; +export default CustomerInfo; diff --git a/src/customers/components/CustomerInfo/index.ts b/src/customers/components/CustomerInfo/index.ts new file mode 100644 index 000000000..5add7b0f4 --- /dev/null +++ b/src/customers/components/CustomerInfo/index.ts @@ -0,0 +1,2 @@ +export { default } from "./CustomerInfo"; +export * from "./CustomerInfo"; diff --git a/src/customers/views/CustomerCreate.tsx b/src/customers/views/CustomerCreate.tsx index 60d729211..404b5fcff 100644 --- a/src/customers/views/CustomerCreate.tsx +++ b/src/customers/views/CustomerCreate.tsx @@ -4,11 +4,10 @@ import { useIntl } from "react-intl"; import { WindowTitle } from "@saleor/components/WindowTitle"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; -import { maybe, transformFormToAddress } from "../../misc"; +import { maybe } from "../../misc"; import CustomerCreatePage from "../components/CustomerCreatePage"; import { TypedCreateCustomerMutation } from "../mutations"; import { TypedCustomerCreateDataQuery } from "../queries"; -import { AddressTypeInput } from "../types"; import { CreateCustomer } from "../types/CreateCustomer"; import { customerListUrl, customerUrl } from "../urls"; @@ -58,43 +57,11 @@ export const CustomerCreate: React.FC<{}> = () => { } onBack={() => navigate(customerListUrl())} onSubmit={formData => { - const areAddressInputFieldsModified = ([ - "city", - "companyName", - "country", - "countryArea", - "firstName", - "lastName", - "phone", - "postalCode", - "streetAddress1", - "streetAddress2" - ] as Array) - .map(key => formData[key]) - .some(field => field !== ""); - - const address = { - city: formData.city, - cityArea: formData.cityArea, - companyName: formData.companyName, - country: formData.country, - countryArea: formData.countryArea, - firstName: formData.firstName, - lastName: formData.lastName, - phone: formData.phone, - postalCode: formData.postalCode, - streetAddress1: formData.streetAddress1, - streetAddress2: formData.streetAddress2 - }; createCustomer({ variables: { input: { - defaultBillingAddress: areAddressInputFieldsModified - ? transformFormToAddress(address) - : null, - defaultShippingAddress: areAddressInputFieldsModified - ? transformFormToAddress(address) - : null, + defaultBillingAddress: formData.address, + defaultShippingAddress: formData.address, email: formData.email, firstName: formData.customerFirstName, lastName: formData.customerLastName, diff --git a/src/hooks/useAddressValidation.ts b/src/hooks/useAddressValidation.ts index 9bd1e401d..ef7ad365a 100644 --- a/src/hooks/useAddressValidation.ts +++ b/src/hooks/useAddressValidation.ts @@ -8,14 +8,14 @@ import { UserError } from "@saleor/types"; import { AddressInput } from "@saleor/types/globalTypes"; import { add, remove } from "@saleor/utils/lists"; -interface UseAddressValidation { +interface UseAddressValidation { errors: UserError[]; - submit: (data: AddressTypeInput) => void; + submit: (data: T & AddressTypeInput) => void; } -function useAddressValidation( - onSubmit: (address: AddressInput) => void -): UseAddressValidation { +function useAddressValidation( + onSubmit: (address: T & AddressInput) => void +): UseAddressValidation { const intl = useIntl(); const [validationErrors, setValidationErrors] = useState([]); @@ -26,7 +26,7 @@ function useAddressValidation( return { errors: validationErrors, - submit: (data: AddressTypeInput) => { + submit: (data: T & AddressTypeInput) => { try { setValidationErrors( remove( diff --git a/src/misc.ts b/src/misc.ts index ff80d36a2..b6f3f958e 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -339,9 +339,9 @@ export function capitalize(s: string) { return s.charAt(0).toLocaleUpperCase() + s.slice(1); } -export function transformFormToAddress( - address: AddressTypeInput -): AddressInput { +export function transformFormToAddress( + address: T & AddressTypeInput +): T & AddressInput { return { ...address, country: findInEnum(address.country, CountryCode) diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index a3993eaca..2ee2c975f 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -31787,11 +31787,11 @@ exports[`Storyshots Views / Customers / Customer details default 1`] = ` - General Informations + elizabeth.vaughn@example.com
- Customer since: May 2017 + Active member since May 2017