Merge branch 'master' into ref/use-apollo-hooks

This commit is contained in:
Marcin Gębala 2019-11-15 14:13:14 +01:00 committed by GitHub
commit 187415feee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1046 additions and 610 deletions

View file

@ -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 - Fix tax settings updating - #243 by @dominik-zeglen
- Add secret fields in plugin configuration - #246 by @dominik-zeglen - Add secret fields in plugin configuration - #246 by @dominik-zeglen
- Fix subcategories pagination - #249 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 - Use Apollo Hooks - #254 by @dominik-zeglen
## 2.0.0 ## 2.0.0

3
_redirects Normal file
View file

@ -0,0 +1,3 @@
# Redirect rules for Netlify
/* /index.html 200

View file

@ -1,6 +1,6 @@
msgid "" msgid ""
msgstr "" 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-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -127,6 +127,14 @@ msgctxt "time during voucher is active, header"
msgid "Active Dates" msgid "Active Dates"
msgstr "" 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 #: build/locale/src/home/components/HomeActivityCard/HomeActivityCard.json
#. [homeActivityCardHeader] - header #. [homeActivityCardHeader] - header
#. defaultMessage is: #. defaultMessage is:
@ -2103,6 +2111,14 @@ msgctxt "subheader"
msgid "Contact Information" msgid "Contact Information"
msgstr "" 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 #: build/locale/src/pages/components/PageInfo/PageInfo.json
#. [src.pages.components.PageInfo.1116746286] - page content #. [src.pages.components.PageInfo.1116746286] - page content
#. defaultMessage is: #. defaultMessage is:
@ -2795,14 +2811,6 @@ msgctxt "description"
msgid "Customer password reset URL" msgid "Customer password reset URL"
msgstr "" 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 #: build/locale/src/intl.json
#. [src.customers] - customers section name #. [src.customers] - customers section name
#. defaultMessage is: #. defaultMessage is:
@ -5943,6 +5951,14 @@ msgctxt "description"
msgid "Permissions" msgid "Permissions"
msgstr "" 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 #: build/locale/src/taxes/components/CountryTaxesPage/CountryTaxesPage.json
#. [src.taxes.components.CountryTaxesPage.815189256] - tax rate #. [src.taxes.components.CountryTaxesPage.815189256] - tax rate
#. defaultMessage is: #. defaultMessage is:

View file

@ -23,6 +23,7 @@ const useStyles = makeStyles(theme => ({
}), }),
title: { title: {
flex: 1, flex: 1,
fontWeight: 500,
lineHeight: 1 lineHeight: 1
}, },
toolbar: { toolbar: {

View file

@ -4,6 +4,7 @@ import React from "react";
import Checkbox from "./Checkbox"; import Checkbox from "./Checkbox";
interface ControlledCheckboxProps { interface ControlledCheckboxProps {
className?: string;
name: string; name: string;
label?: React.ReactNode; label?: React.ReactNode;
checked: boolean; checked: boolean;
@ -16,7 +17,8 @@ export const ControlledCheckbox: React.FC<ControlledCheckboxProps> = ({
disabled, disabled,
name, name,
label, label,
onChange onChange,
...props
}) => ( }) => (
<FormControlLabel <FormControlLabel
disabled={disabled} disabled={disabled}
@ -29,6 +31,7 @@ export const ControlledCheckbox: React.FC<ControlledCheckboxProps> = ({
/> />
} }
label={label} label={label}
{...props}
/> />
); );
ControlledCheckbox.displayName = "ControlledCheckbox"; ControlledCheckbox.displayName = "ControlledCheckbox";

View file

@ -9,7 +9,9 @@ import Form from "@saleor/components/Form";
import Grid from "@saleor/components/Grid"; import Grid from "@saleor/components/Grid";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import useAddressValidation from "@saleor/hooks/useAddressValidation";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { AddressInput } from "@saleor/types/globalTypes";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import { UserError } from "../../../types"; import { UserError } from "../../../types";
import { AddressTypeInput } from "../../types"; import { AddressTypeInput } from "../../types";
@ -18,14 +20,18 @@ import CustomerCreateAddress from "../CustomerCreateAddress/CustomerCreateAddres
import CustomerCreateDetails from "../CustomerCreateDetails"; import CustomerCreateDetails from "../CustomerCreateDetails";
import CustomerCreateNote from "../CustomerCreateNote/CustomerCreateNote"; import CustomerCreateNote from "../CustomerCreateNote/CustomerCreateNote";
export interface CustomerCreatePageFormData extends AddressTypeInput { export interface CustomerCreatePageFormData {
customerFirstName: string; customerFirstName: string;
customerLastName: string; customerLastName: string;
email: string; email: string;
note: string; note: string;
} }
export interface CustomerCreatePageSubmitData
extends CustomerCreatePageFormData {
address: AddressInput;
}
const initialForm: CustomerCreatePageFormData = { const initialForm: CustomerCreatePageFormData & AddressTypeInput = {
city: "", city: "",
cityArea: "", cityArea: "",
companyName: "", companyName: "",
@ -49,7 +55,7 @@ export interface CustomerCreatePageProps {
errors: UserError[]; errors: UserError[];
saveButtonBar: ConfirmButtonTransitionState; saveButtonBar: ConfirmButtonTransitionState;
onBack: () => void; onBack: () => void;
onSubmit: (data: CustomerCreatePageFormData) => void; onSubmit: (data: CustomerCreatePageSubmitData) => void;
} }
const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({ const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
@ -67,12 +73,67 @@ const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
label: country.country, label: country.country,
value: country.code value: country.code
})); }));
const {
errors: validationErrors,
submit: handleSubmitWithAddress
} = useAddressValidation<CustomerCreatePageFormData>(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<keyof AddressTypeInput>)
.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 ( return (
<Form <Form
initial={initialForm} initial={initialForm}
onSubmit={onSubmit} onSubmit={handleSubmit}
errors={errors} errors={[...errors, ...validationErrors]}
confirmLeave confirmLeave
> >
{({ change, data, errors: formErrors, hasChanged, submit }) => { {({ change, data, errors: formErrors, hasChanged, submit }) => {

View file

@ -9,39 +9,34 @@ import { FormattedMessage, useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox"; import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
import { FormSpacer } from "@saleor/components/FormSpacer";
import Skeleton from "@saleor/components/Skeleton"; 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"; import { CustomerDetails_user } from "../../types/CustomerDetails";
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
cardTitle: { cardTitle: {
height: 64 height: 72
}, },
root: { checkbox: {
display: "grid" as "grid", marginBottom: theme.spacing()
gridColumnGap: theme.spacing(2), },
gridRowGap: theme.spacing(3), content: {
gridTemplateColumns: "1fr 1fr" paddingTop: theme.spacing()
},
subtitle: {
marginTop: theme.spacing()
} }
})); }));
export interface CustomerDetailsProps { export interface CustomerDetailsProps {
customer: CustomerDetails_user; customer: CustomerDetails_user;
data: { data: {
firstName: string;
lastName: string;
email: string;
isActive: boolean; isActive: boolean;
note: string; note: string;
}; };
disabled: boolean; disabled: boolean;
errors: { errors: FormErrors<"isActive" | "note">;
firstName?: string;
lastName?: string;
email?: string;
note?: string;
};
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -57,11 +52,15 @@ const CustomerDetails: React.FC<CustomerDetailsProps> = props => {
className={classes.cardTitle} className={classes.cardTitle}
title={ title={
<> <>
<FormattedMessage {...commonMessages.generalInformations} /> {maybe<React.ReactNode>(() => customer.email, <Skeleton />)}
{customer && customer.dateJoined ? ( {customer && customer.dateJoined ? (
<Typography variant="caption" component="div"> <Typography
className={classes.subtitle}
variant="caption"
component="div"
>
<FormattedMessage <FormattedMessage
defaultMessage="Customer since: {date}" defaultMessage="Active member since {date}"
description="section subheader" description="section subheader"
values={{ values={{
date: moment(customer.dateJoined).format("MMM YYYY") date: moment(customer.dateJoined).format("MMM YYYY")
@ -74,9 +73,10 @@ const CustomerDetails: React.FC<CustomerDetailsProps> = props => {
</> </>
} }
/> />
<CardContent> <CardContent className={classes.content}>
<ControlledCheckbox <ControlledCheckbox
checked={data.isActive} checked={data.isActive}
className={classes.checkbox}
disabled={disabled} disabled={disabled}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "User account active", defaultMessage: "User account active",
@ -85,44 +85,6 @@ const CustomerDetails: React.FC<CustomerDetailsProps> = props => {
name="isActive" name="isActive"
onChange={onChange} onChange={onChange}
/> />
<FormSpacer />
<div className={classes.root}>
<TextField
disabled={disabled}
error={!!errors.firstName}
fullWidth
helperText={errors.firstName}
name="firstName"
type="text"
label={intl.formatMessage(commonMessages.firstName)}
value={data.firstName}
onChange={onChange}
/>
<TextField
disabled={disabled}
error={!!errors.lastName}
fullWidth
helperText={errors.lastName}
name="lastName"
type="text"
label={intl.formatMessage(commonMessages.lastName)}
value={data.lastName}
onChange={onChange}
/>
</div>
<FormSpacer />
<TextField
disabled={disabled}
error={!!errors.email}
fullWidth
helperText={errors.email}
name="email"
type="email"
label={intl.formatMessage(commonMessages.email)}
value={data.email}
onChange={onChange}
/>
<FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.note} error={!!errors.note}

View file

@ -13,10 +13,11 @@ import { sectionNames } from "@saleor/intl";
import { getUserName, maybe } from "../../../misc"; import { getUserName, maybe } from "../../../misc";
import { UserError } from "../../../types"; import { UserError } from "../../../types";
import { CustomerDetails_user } from "../../types/CustomerDetails"; import { CustomerDetails_user } from "../../types/CustomerDetails";
import CustomerAddresses from "../CustomerAddresses/CustomerAddresses"; import CustomerAddresses from "../CustomerAddresses";
import CustomerDetails from "../CustomerDetails/CustomerDetails"; import CustomerDetails from "../CustomerDetails";
import CustomerOrders from "../CustomerOrders/CustomerOrders"; import CustomerInfo from "../CustomerInfo";
import CustomerStats from "../CustomerStats/CustomerStats"; import CustomerOrders from "../CustomerOrders";
import CustomerStats from "../CustomerStats";
export interface CustomerDetailsPageFormData { export interface CustomerDetailsPageFormData {
firstName: string; firstName: string;
@ -82,6 +83,13 @@ const CustomerDetailsPage: React.FC<CustomerDetailsPageProps> = ({
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
<CustomerInfo
data={data}
disabled={disabled}
errors={formErrors}
onChange={change}
/>
<CardSpacer />
<CustomerOrders <CustomerOrders
orders={maybe(() => orders={maybe(() =>
customer.orders.edges.map(edge => edge.node) customer.orders.edges.map(edge => edge.node)

View file

@ -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<any>) => void;
}
const CustomerInfo: React.FC<CustomerInfoProps> = props => {
const { data, disabled, errors, onChange } = props;
const classes = useStyles(props);
const intl = useIntl();
return (
<Card>
<CardTitle
title={
<FormattedMessage
defaultMessage="Personal Informations"
description="customer informations, header"
/>
}
/>
<CardContent className={classes.content}>
<Typography className={classes.sectionHeader}>
<FormattedMessage {...commonMessages.generalInformations} />
</Typography>
<Grid variant="uniform">
<TextField
disabled={disabled}
error={!!errors.firstName}
fullWidth
helperText={errors.firstName}
name="firstName"
type="text"
label={intl.formatMessage(commonMessages.firstName)}
value={data.firstName}
onChange={onChange}
/>
<TextField
disabled={disabled}
error={!!errors.lastName}
fullWidth
helperText={errors.lastName}
name="lastName"
type="text"
label={intl.formatMessage(commonMessages.lastName)}
value={data.lastName}
onChange={onChange}
/>
</Grid>
<Hr className={classes.hr} />
<Typography className={classes.sectionHeader}>
<FormattedMessage
defaultMessage="Contact Informations"
description="customer contact section, header"
/>
</Typography>
<TextField
disabled={disabled}
error={!!errors.email}
fullWidth
helperText={errors.email}
name="email"
type="email"
label={intl.formatMessage(commonMessages.email)}
value={data.email}
onChange={onChange}
/>
</CardContent>
</Card>
);
};
CustomerInfo.displayName = "CustomerInfo";
export default CustomerInfo;

View file

@ -0,0 +1,2 @@
export { default } from "./CustomerInfo";
export * from "./CustomerInfo";

View file

@ -4,11 +4,10 @@ import { useIntl } from "react-intl";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import { maybe, transformFormToAddress } from "../../misc"; import { maybe } from "../../misc";
import CustomerCreatePage from "../components/CustomerCreatePage"; import CustomerCreatePage from "../components/CustomerCreatePage";
import { TypedCreateCustomerMutation } from "../mutations"; import { TypedCreateCustomerMutation } from "../mutations";
import { TypedCustomerCreateDataQuery } from "../queries"; import { TypedCustomerCreateDataQuery } from "../queries";
import { AddressTypeInput } from "../types";
import { CreateCustomer } from "../types/CreateCustomer"; import { CreateCustomer } from "../types/CreateCustomer";
import { customerListUrl, customerUrl } from "../urls"; import { customerListUrl, customerUrl } from "../urls";
@ -58,43 +57,11 @@ export const CustomerCreate: React.FC<{}> = () => {
} }
onBack={() => navigate(customerListUrl())} onBack={() => navigate(customerListUrl())}
onSubmit={formData => { onSubmit={formData => {
const areAddressInputFieldsModified = ([
"city",
"companyName",
"country",
"countryArea",
"firstName",
"lastName",
"phone",
"postalCode",
"streetAddress1",
"streetAddress2"
] as Array<keyof AddressTypeInput>)
.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({ createCustomer({
variables: { variables: {
input: { input: {
defaultBillingAddress: areAddressInputFieldsModified defaultBillingAddress: formData.address,
? transformFormToAddress(address) defaultShippingAddress: formData.address,
: null,
defaultShippingAddress: areAddressInputFieldsModified
? transformFormToAddress(address)
: null,
email: formData.email, email: formData.email,
firstName: formData.customerFirstName, firstName: formData.customerFirstName,
lastName: formData.customerLastName, lastName: formData.customerLastName,

View file

@ -8,14 +8,14 @@ import { UserError } from "@saleor/types";
import { AddressInput } from "@saleor/types/globalTypes"; import { AddressInput } from "@saleor/types/globalTypes";
import { add, remove } from "@saleor/utils/lists"; import { add, remove } from "@saleor/utils/lists";
interface UseAddressValidation { interface UseAddressValidation<T> {
errors: UserError[]; errors: UserError[];
submit: (data: AddressTypeInput) => void; submit: (data: T & AddressTypeInput) => void;
} }
function useAddressValidation( function useAddressValidation<T>(
onSubmit: (address: AddressInput) => void onSubmit: (address: T & AddressInput) => void
): UseAddressValidation { ): UseAddressValidation<T> {
const intl = useIntl(); const intl = useIntl();
const [validationErrors, setValidationErrors] = useState<UserError[]>([]); const [validationErrors, setValidationErrors] = useState<UserError[]>([]);
@ -26,7 +26,7 @@ function useAddressValidation(
return { return {
errors: validationErrors, errors: validationErrors,
submit: (data: AddressTypeInput) => { submit: (data: T & AddressTypeInput) => {
try { try {
setValidationErrors( setValidationErrors(
remove( remove(

View file

@ -339,9 +339,9 @@ export function capitalize(s: string) {
return s.charAt(0).toLocaleUpperCase() + s.slice(1); return s.charAt(0).toLocaleUpperCase() + s.slice(1);
} }
export function transformFormToAddress( export function transformFormToAddress<T>(
address: AddressTypeInput address: T & AddressTypeInput
): AddressInput { ): T & AddressInput {
return { return {
...address, ...address,
country: findInEnum(address.country, CountryCode) country: findInEnum(address.country, CountryCode)

File diff suppressed because it is too large Load diff

View file

@ -342,6 +342,9 @@ export default (colors: IThemeColors): Theme =>
} }
} }
}, },
message: {
fontSize: 16
},
root: { root: {
backgroundColor: colors.background.paper, backgroundColor: colors.background.paper,
boxShadow: boxShadow:
@ -397,12 +400,12 @@ export default (colors: IThemeColors): Theme =>
}, },
MuiTableCell: { MuiTableCell: {
body: { body: {
fontSize: ".875rem", fontSize: "1rem",
paddingBottom: 8, paddingBottom: 8,
paddingTop: 8 paddingTop: 8
}, },
head: { head: {
fontSize: ".875rem" fontSize: "1rem"
}, },
paddingCheckbox: { paddingCheckbox: {
"&:first-child": { "&:first-child": {