* Searching addresses in order drafts (#1618) * wip search addresses * wip fix query regex verification * wip move dialog title & add selected label * wip move dialog title * wip edit icon * wip address selection logic * wip change messages * wip message when search is unsuccesful * wip add billing address change support * wip default address validation & cleanup * wip run tests & extract messages * wip bump macaw * tests & cleanup * fix scrollbars * remove address card wrapper * apply code review suggestions * remove comments * Bump macaw to 0.2.7 * Fix outside modal click state retention
This commit is contained in:
parent
bdaeb8b621
commit
78f7b5d4fb
13 changed files with 468 additions and 129 deletions
|
@ -4047,7 +4047,11 @@
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderCustomerAddressesEditDialog_dot_billingSameAsShipping": {
|
"src_dot_orders_dot_components_dot_OrderCustomerAddressesEditDialog_dot_billingSameAsShipping": {
|
||||||
"context": "checkbox label",
|
"context": "checkbox label",
|
||||||
"string": "Billing address same as shipping address"
|
"string": "Set the same for billing address"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderCustomerAddressesEditDialog_dot_billingTitle": {
|
||||||
|
"context": "search modal billing title",
|
||||||
|
"string": "Billing address"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderCustomerAddressesEditDialog_dot_customerAddress": {
|
"src_dot_orders_dot_components_dot_OrderCustomerAddressesEditDialog_dot_customerAddress": {
|
||||||
"context": "address type",
|
"context": "address type",
|
||||||
|
@ -4065,13 +4069,24 @@
|
||||||
"context": "address type",
|
"context": "address type",
|
||||||
"string": "Add new address"
|
"string": "Add new address"
|
||||||
},
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderCustomerAddressesEditDialog_dot_noResultsFound": {
|
||||||
|
"string": "No results found"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderCustomerAddressesEditDialog_dot_searchInfo": {
|
||||||
|
"context": "modal information under title",
|
||||||
|
"string": "Select an address you want to use from the list below"
|
||||||
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderCustomerAddressesEditDialog_dot_shippingAddressDescription": {
|
"src_dot_orders_dot_components_dot_OrderCustomerAddressesEditDialog_dot_shippingAddressDescription": {
|
||||||
"context": "dialog content",
|
"context": "dialog content",
|
||||||
"string": "This customer doesn’t have any shipping addresses. Provide address for order:"
|
"string": "This customer doesn’t have any shipping addresses. Provide address for order:"
|
||||||
},
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderCustomerAddressesEditDialog_dot_shippingTitle": {
|
||||||
|
"context": "search modal shipping title",
|
||||||
|
"string": "Shipping address"
|
||||||
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderCustomerAddressesEditDialog_dot_title": {
|
"src_dot_orders_dot_components_dot_OrderCustomerAddressesEditDialog_dot_title": {
|
||||||
"context": "dialog header",
|
"context": "dialog header",
|
||||||
"string": "Shipping address for order"
|
"string": "Change address for order"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderCustomerChangeDialog_dot_changeAddress": {
|
"src_dot_orders_dot_components_dot_OrderCustomerChangeDialog_dot_changeAddress": {
|
||||||
"context": "option label",
|
"context": "option label",
|
||||||
|
@ -6748,6 +6763,9 @@
|
||||||
"context": "select all options, button",
|
"context": "select all options, button",
|
||||||
"string": "Select All"
|
"string": "Select All"
|
||||||
},
|
},
|
||||||
|
"src_dot_selected": {
|
||||||
|
"string": "Selected"
|
||||||
|
},
|
||||||
"src_dot_send": {
|
"src_dot_send": {
|
||||||
"context": "button",
|
"context": "button",
|
||||||
"string": "Send"
|
"string": "Send"
|
||||||
|
|
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -5166,9 +5166,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@saleor/macaw-ui": {
|
"@saleor/macaw-ui": {
|
||||||
"version": "0.2.6",
|
"version": "0.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.2.7.tgz",
|
||||||
"integrity": "sha512-ySEhWN9kyxX+5ATXVOg4siS5RwMRIMcLAybJHax0SpzS4E6FtLM5VDQhWliHaM2hicFPMVrLBa7MkTvAV1JcIA==",
|
"integrity": "sha512-sRt193W5u1Vu+5893zEsRbkVF3H1uXuqNC+BEXwTg85i+J9YHmykJZjDneExgGH24nYe948S5dffsy3NMOGSTQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.58",
|
"@material-ui/lab": "^4.0.0-alpha.58",
|
||||||
"@material-ui/styles": "^4.11.4",
|
"@material-ui/styles": "^4.11.4",
|
||||||
"@saleor/macaw-ui": "^0.2.6",
|
"@saleor/macaw-ui": "^0.2.7",
|
||||||
"@sentry/react": "^6.0.0",
|
"@sentry/react": "^6.0.0",
|
||||||
"@types/faker": "^5.1.6",
|
"@types/faker": "^5.1.6",
|
||||||
"@uiw/react-color-hue": "0.0.34",
|
"@uiw/react-color-hue": "0.0.34",
|
||||||
|
|
|
@ -1,30 +1,48 @@
|
||||||
import { Card, CardContent } from "@material-ui/core";
|
import { Card, CardContent, Typography } from "@material-ui/core";
|
||||||
import AddressFormatter from "@saleor/components/AddressFormatter";
|
import AddressFormatter from "@saleor/components/AddressFormatter";
|
||||||
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import { EditIcon } from "@saleor/macaw-ui";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { CustomerAddresses_user_addresses } from "../../types/CustomerAddresses";
|
import { CustomerAddresses_user_addresses } from "../../types/CustomerAddresses";
|
||||||
import { useStyles } from "./styles";
|
import { useStyles } from "./styles";
|
||||||
|
|
||||||
export interface CustomerAddressChoiceCardProps {
|
export interface CustomerAddressChoiceCardProps {
|
||||||
address: CustomerAddresses_user_addresses;
|
address: CustomerAddresses_user_addresses;
|
||||||
selected: boolean;
|
selected?: boolean;
|
||||||
onSelect: () => void;
|
editable?: boolean;
|
||||||
|
onSelect?: () => void;
|
||||||
|
onEditClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomerAddressChoiceCard: React.FC<CustomerAddressChoiceCardProps> = props => {
|
const CustomerAddressChoiceCard: React.FC<CustomerAddressChoiceCardProps> = props => {
|
||||||
const { address, selected, onSelect } = props;
|
const { address, selected, editable, onSelect, onEditClick } = props;
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={classNames(classes.card, {
|
className={classNames(classes.card, {
|
||||||
[classes.cardSelected]: selected
|
[classes.cardSelected]: selected,
|
||||||
|
[classes.selectableCard]: !editable
|
||||||
})}
|
})}
|
||||||
onClick={onSelect}
|
onClick={onSelect}
|
||||||
>
|
>
|
||||||
<CardContent>
|
<CardContent className={classes.cardContent}>
|
||||||
<AddressFormatter address={address} />
|
<AddressFormatter address={address} />
|
||||||
|
{editable && (
|
||||||
|
<div onClick={onEditClick}>
|
||||||
|
<EditIcon className={classes.editIcon} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{selected && (
|
||||||
|
<Typography color="primary" className={classes.selectedLabel}>
|
||||||
|
{intl.formatMessage(commonMessages.selected)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,13 +3,37 @@ import { makeStyles } from "@saleor/macaw-ui";
|
||||||
export const useStyles = makeStyles(
|
export const useStyles = makeStyles(
|
||||||
theme => ({
|
theme => ({
|
||||||
card: {
|
card: {
|
||||||
cursor: "pointer",
|
|
||||||
padding: "1px"
|
padding: "1px"
|
||||||
},
|
},
|
||||||
cardSelected: {
|
cardSelected: {
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.main,
|
||||||
borderWidth: "2px",
|
borderWidth: "2px",
|
||||||
padding: "0"
|
padding: "0"
|
||||||
|
},
|
||||||
|
cardContent: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "flex-start"
|
||||||
|
},
|
||||||
|
selectableCard: {
|
||||||
|
"&:hover": {
|
||||||
|
cursor: "pointer",
|
||||||
|
borderColor: theme.palette.primary.main
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedLabel: {
|
||||||
|
fontSize: "1.4rem",
|
||||||
|
lineHeight: "1.75",
|
||||||
|
fontWeight: 600,
|
||||||
|
textTransform: "uppercase"
|
||||||
|
},
|
||||||
|
editIcon: {
|
||||||
|
color: theme.palette.grey[600],
|
||||||
|
"&:hover": {
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
cursor: "pointer"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
{ name: "CustomerAddressChoiceCard" }
|
{ name: "CustomerAddressChoiceCard" }
|
||||||
|
|
|
@ -84,6 +84,9 @@ export const commonMessages = defineMessages({
|
||||||
savedChanges: {
|
savedChanges: {
|
||||||
defaultMessage: "Saved changes"
|
defaultMessage: "Saved changes"
|
||||||
},
|
},
|
||||||
|
selected: {
|
||||||
|
defaultMessage: "Selected"
|
||||||
|
},
|
||||||
sessionExpired: {
|
sessionExpired: {
|
||||||
defaultMessage: "Your session has expired. Please log in again to continue."
|
defaultMessage: "Your session has expired. Please log in again to continue."
|
||||||
},
|
},
|
||||||
|
|
15
src/misc.ts
15
src/misc.ts
|
@ -434,3 +434,18 @@ export function getFullName<T extends { firstName: string; lastName: string }>(
|
||||||
|
|
||||||
return `${data.firstName} ${data.lastName}`;
|
return `${data.firstName} ${data.lastName}`;
|
||||||
}
|
}
|
||||||
|
export const flatten = (obj: unknown) => {
|
||||||
|
// Be cautious that repeated keys are overwritten
|
||||||
|
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
Object.keys(obj).forEach(key => {
|
||||||
|
if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||||
|
Object.assign(result, flatten(obj[key]));
|
||||||
|
} else {
|
||||||
|
result[key] = obj[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { FormChange } from "@saleor/hooks/useForm";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import { getById } from "../OrderReturnPage/utils";
|
||||||
import { AddressInputOptionEnum } from "./form";
|
import { AddressInputOptionEnum } from "./form";
|
||||||
import { addressEditMessages } from "./messages";
|
import { addressEditMessages } from "./messages";
|
||||||
import { useStyles } from "./styles";
|
import { useStyles } from "./styles";
|
||||||
|
@ -24,15 +25,13 @@ export interface OrderCustomerAddressEditProps {
|
||||||
addressInputOption: AddressInputOptionEnum;
|
addressInputOption: AddressInputOptionEnum;
|
||||||
addressInputName: string;
|
addressInputName: string;
|
||||||
onChangeAddressInputOption: FormChange;
|
onChangeAddressInputOption: FormChange;
|
||||||
customerAddressId: string;
|
selectedCustomerAddressId: string;
|
||||||
formAddress: AddressTypeInput;
|
formAddress: AddressTypeInput;
|
||||||
formAddressCountryDisplayName: string;
|
formAddressCountryDisplayName: string;
|
||||||
formErrors: Array<AccountErrorFragment | OrderErrorFragment>;
|
formErrors: Array<AccountErrorFragment | OrderErrorFragment>;
|
||||||
onChangeCustomerAddress: (
|
|
||||||
customerAddress: CustomerAddresses_user_addresses
|
|
||||||
) => void;
|
|
||||||
onChangeFormAddress: (event: React.ChangeEvent<any>) => void;
|
onChangeFormAddress: (event: React.ChangeEvent<any>) => void;
|
||||||
onChangeFormAddressCountry: (event: React.ChangeEvent<any>) => void;
|
onChangeFormAddressCountry: (event: React.ChangeEvent<any>) => void;
|
||||||
|
onEdit?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrderCustomerAddressEdit: React.FC<OrderCustomerAddressEditProps> = props => {
|
const OrderCustomerAddressEdit: React.FC<OrderCustomerAddressEditProps> = props => {
|
||||||
|
@ -43,13 +42,13 @@ const OrderCustomerAddressEdit: React.FC<OrderCustomerAddressEditProps> = props
|
||||||
addressInputOption,
|
addressInputOption,
|
||||||
addressInputName,
|
addressInputName,
|
||||||
onChangeAddressInputOption,
|
onChangeAddressInputOption,
|
||||||
customerAddressId,
|
selectedCustomerAddressId,
|
||||||
formAddress,
|
formAddress,
|
||||||
formAddressCountryDisplayName,
|
formAddressCountryDisplayName,
|
||||||
formErrors,
|
formErrors,
|
||||||
onChangeCustomerAddress,
|
|
||||||
onChangeFormAddress,
|
onChangeFormAddress,
|
||||||
onChangeFormAddressCountry
|
onChangeFormAddressCountry,
|
||||||
|
onEdit
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
@ -92,19 +91,15 @@ const OrderCustomerAddressEdit: React.FC<OrderCustomerAddressEditProps> = props
|
||||||
className={classes.optionLabel}
|
className={classes.optionLabel}
|
||||||
/>
|
/>
|
||||||
{addressInputOption === AddressInputOptionEnum.CUSTOMER_ADDRESS && (
|
{addressInputOption === AddressInputOptionEnum.CUSTOMER_ADDRESS && (
|
||||||
<div className={classes.scrollableWrapper}>
|
<>
|
||||||
{customerAddresses.map(customerAddress => (
|
|
||||||
<React.Fragment key={customerAddress.id}>
|
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<CustomerAddressChoiceCard
|
<CustomerAddressChoiceCard
|
||||||
address={customerAddress}
|
address={customerAddresses.find(getById(selectedCustomerAddressId))}
|
||||||
selected={customerAddress.id === customerAddressId}
|
editable
|
||||||
onSelect={() => onChangeCustomerAddress(customerAddress)}
|
onEditClick={onEdit}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
</div>
|
</>
|
||||||
)}
|
)}
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={AddressInputOptionEnum.NEW_ADDRESS}
|
value={AddressInputOptionEnum.NEW_ADDRESS}
|
||||||
|
|
|
@ -36,8 +36,14 @@ import OrderCustomerAddressesEditForm, {
|
||||||
} from "./form";
|
} from "./form";
|
||||||
import { dialogMessages } from "./messages";
|
import { dialogMessages } from "./messages";
|
||||||
import OrderCustomerAddressEdit from "./OrderCustomerAddressEdit";
|
import OrderCustomerAddressEdit from "./OrderCustomerAddressEdit";
|
||||||
|
import OrderCustomerAddressesSearch from "./OrderCustomerAddressesSearch";
|
||||||
import { useStyles } from "./styles";
|
import { useStyles } from "./styles";
|
||||||
|
import { validateDefaultAddress } from "./utils";
|
||||||
|
|
||||||
|
export interface OrderCustomerSearchAddressState {
|
||||||
|
open: boolean;
|
||||||
|
type: AddressTypeEnum;
|
||||||
|
}
|
||||||
export interface OrderCustomerAddressesEditDialogOutput {
|
export interface OrderCustomerAddressesEditDialogOutput {
|
||||||
shippingAddress: AddressInput;
|
shippingAddress: AddressInput;
|
||||||
billingAddress: AddressInput;
|
billingAddress: AddressInput;
|
||||||
|
@ -56,6 +62,11 @@ export interface OrderCustomerAddressesEditDialogProps {
|
||||||
onConfirm(data: OrderCustomerAddressesEditDialogOutput): SubmitPromise;
|
onConfirm(data: OrderCustomerAddressesEditDialogOutput): SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultSearchState: OrderCustomerSearchAddressState = {
|
||||||
|
open: false,
|
||||||
|
type: undefined
|
||||||
|
};
|
||||||
|
|
||||||
const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialogProps> = props => {
|
const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialogProps> = props => {
|
||||||
const {
|
const {
|
||||||
open,
|
open,
|
||||||
|
@ -85,9 +96,11 @@ const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialo
|
||||||
open
|
open
|
||||||
);
|
);
|
||||||
|
|
||||||
const getCustomerAddress = (customerAddressId: string): AddressInput =>
|
const getCustomerAddress = (
|
||||||
|
selectedCustomerAddressID: string
|
||||||
|
): AddressInput =>
|
||||||
transformAddressToAddressInput(
|
transformAddressToAddressInput(
|
||||||
customerAddresses.find(getById(customerAddressId))
|
customerAddresses.find(getById(selectedCustomerAddressID))
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleAddressesSubmit = (data: OrderCustomerAddressesEditFormData) => {
|
const handleAddressesSubmit = (data: OrderCustomerAddressesEditFormData) => {
|
||||||
|
@ -127,15 +140,60 @@ const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialo
|
||||||
|
|
||||||
const countryChoices = mapCountriesToChoices(countries);
|
const countryChoices = mapCountriesToChoices(countries);
|
||||||
|
|
||||||
|
const [addressSearchState, setAddressSearchState] = React.useState<
|
||||||
|
OrderCustomerSearchAddressState
|
||||||
|
>(defaultSearchState);
|
||||||
|
|
||||||
|
const validatedDefaultShippingAddress = validateDefaultAddress(
|
||||||
|
defaultShippingAddress,
|
||||||
|
customerAddresses
|
||||||
|
);
|
||||||
|
const validatedDefaultBillingAddress = validateDefaultAddress(
|
||||||
|
defaultBillingAddress,
|
||||||
|
customerAddresses
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog onClose={onClose} open={open}>
|
<Dialog
|
||||||
|
onClose={() => {
|
||||||
|
setAddressSearchState(defaultSearchState);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
open={open}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
<OrderCustomerAddressesEditForm
|
<OrderCustomerAddressesEditForm
|
||||||
countryChoices={countryChoices}
|
countryChoices={countryChoices}
|
||||||
defaultShippingAddress={defaultShippingAddress}
|
defaultShippingAddress={validatedDefaultShippingAddress}
|
||||||
defaultBillingAddress={defaultBillingAddress}
|
defaultBillingAddress={validatedDefaultBillingAddress}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
{({ change, data, handlers }) => (
|
{({ change, data, handlers }) => (
|
||||||
|
<>
|
||||||
|
{addressSearchState.open ? (
|
||||||
|
<OrderCustomerAddressesSearch
|
||||||
|
type={addressSearchState?.type}
|
||||||
|
customerAddresses={customerAddresses}
|
||||||
|
selectedCustomerAddressId={
|
||||||
|
addressSearchState.type === AddressTypeEnum.SHIPPING
|
||||||
|
? data.customerShippingAddress?.id
|
||||||
|
: data.customerBillingAddress?.id
|
||||||
|
}
|
||||||
|
onChangeCustomerShippingAddress={customerAddress =>
|
||||||
|
handlers.changeCustomerAddress(
|
||||||
|
customerAddress,
|
||||||
|
"customerShippingAddress"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onChangeCustomerBillingAddress={customerAddress =>
|
||||||
|
handlers.changeCustomerAddress(
|
||||||
|
customerAddress,
|
||||||
|
"customerBillingAddress"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
exitSearch={() => setAddressSearchState(defaultSearchState)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<>
|
<>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<FormattedMessage {...dialogMessages.title} />
|
<FormattedMessage {...dialogMessages.title} />
|
||||||
|
@ -160,22 +218,24 @@ const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialo
|
||||||
addressInputName="shippingAddressInputOption"
|
addressInputName="shippingAddressInputOption"
|
||||||
onChangeAddressInputOption={change}
|
onChangeAddressInputOption={change}
|
||||||
customerAddresses={customerAddresses}
|
customerAddresses={customerAddresses}
|
||||||
customerAddressId={data.customerShippingAddress?.id}
|
selectedCustomerAddressId={data.customerShippingAddress?.id}
|
||||||
formAddress={data.shippingAddress}
|
formAddress={data.shippingAddress}
|
||||||
formAddressCountryDisplayName={data.shippingCountryDisplayName}
|
formAddressCountryDisplayName={
|
||||||
|
data.shippingCountryDisplayName
|
||||||
|
}
|
||||||
formErrors={dialogErrors.filter(
|
formErrors={dialogErrors.filter(
|
||||||
error => error.addressType === AddressTypeEnum.SHIPPING
|
error => error.addressType === AddressTypeEnum.SHIPPING
|
||||||
)}
|
)}
|
||||||
onChangeCustomerAddress={customerAddress =>
|
|
||||||
handlers.changeCustomerAddress(
|
|
||||||
customerAddress,
|
|
||||||
"customerShippingAddress"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onChangeFormAddress={event =>
|
onChangeFormAddress={event =>
|
||||||
handlers.changeFormAddress(event, "shippingAddress")
|
handlers.changeFormAddress(event, "shippingAddress")
|
||||||
}
|
}
|
||||||
onChangeFormAddressCountry={handlers.selectShippingCountry}
|
onChangeFormAddressCountry={handlers.selectShippingCountry}
|
||||||
|
onEdit={() =>
|
||||||
|
setAddressSearchState({
|
||||||
|
open: true,
|
||||||
|
type: AddressTypeEnum.SHIPPING
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
<Divider />
|
<Divider />
|
||||||
|
@ -196,7 +256,9 @@ const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialo
|
||||||
data-test="billingSameAsShipping"
|
data-test="billingSameAsShipping"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={intl.formatMessage(dialogMessages.billingSameAsShipping)}
|
label={intl.formatMessage(
|
||||||
|
dialogMessages.billingSameAsShipping
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
{!data.billingSameAsShipping && (
|
{!data.billingSameAsShipping && (
|
||||||
<>
|
<>
|
||||||
|
@ -220,7 +282,9 @@ const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialo
|
||||||
addressInputName="billingAddressInputOption"
|
addressInputName="billingAddressInputOption"
|
||||||
onChangeAddressInputOption={change}
|
onChangeAddressInputOption={change}
|
||||||
customerAddresses={customerAddresses}
|
customerAddresses={customerAddresses}
|
||||||
customerAddressId={data.customerBillingAddress?.id}
|
selectedCustomerAddressId={
|
||||||
|
data.customerBillingAddress?.id
|
||||||
|
}
|
||||||
formAddress={data.billingAddress}
|
formAddress={data.billingAddress}
|
||||||
formAddressCountryDisplayName={
|
formAddressCountryDisplayName={
|
||||||
data.billingCountryDisplayName
|
data.billingCountryDisplayName
|
||||||
|
@ -228,16 +292,18 @@ const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialo
|
||||||
formErrors={dialogErrors.filter(
|
formErrors={dialogErrors.filter(
|
||||||
error => error.addressType === AddressTypeEnum.BILLING
|
error => error.addressType === AddressTypeEnum.BILLING
|
||||||
)}
|
)}
|
||||||
onChangeCustomerAddress={customerAddress =>
|
|
||||||
handlers.changeCustomerAddress(
|
|
||||||
customerAddress,
|
|
||||||
"customerBillingAddress"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onChangeFormAddress={event =>
|
onChangeFormAddress={event =>
|
||||||
handlers.changeFormAddress(event, "billingAddress")
|
handlers.changeFormAddress(event, "billingAddress")
|
||||||
}
|
}
|
||||||
onChangeFormAddressCountry={handlers.selectBillingCountry}
|
onChangeFormAddressCountry={
|
||||||
|
handlers.selectBillingCountry
|
||||||
|
}
|
||||||
|
onEdit={() =>
|
||||||
|
setAddressSearchState({
|
||||||
|
open: true,
|
||||||
|
type: AddressTypeEnum.BILLING
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -250,11 +316,13 @@ const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialo
|
||||||
type="submit"
|
type="submit"
|
||||||
data-test="submit"
|
data-test="submit"
|
||||||
>
|
>
|
||||||
<FormattedMessage {...buttonMessages.select} />
|
<FormattedMessage {...buttonMessages.save} />
|
||||||
</ConfirmButton>
|
</ConfirmButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</OrderCustomerAddressesEditForm>
|
</OrderCustomerAddressesEditForm>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
InputAdornment,
|
||||||
|
TextField
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import CardSpacer from "@saleor/components/CardSpacer";
|
||||||
|
import { ConfirmButton } from "@saleor/components/ConfirmButton";
|
||||||
|
import CustomerAddressChoiceCard from "@saleor/customers/components/CustomerAddressChoiceCard";
|
||||||
|
import { CustomerAddresses_user_addresses } from "@saleor/customers/types/CustomerAddresses";
|
||||||
|
import { buttonMessages } from "@saleor/intl";
|
||||||
|
import { SearchIcon } from "@saleor/macaw-ui";
|
||||||
|
import { AddressTypeEnum } from "@saleor/types/globalTypes";
|
||||||
|
import React from "react";
|
||||||
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import { getById } from "../OrderReturnPage/utils";
|
||||||
|
import { addressSearchMessages as messages } from "./messages";
|
||||||
|
import { useStyles } from "./styles";
|
||||||
|
import { parseQuery, stringifyAddress } from "./utils";
|
||||||
|
|
||||||
|
export interface OrderCustomerAddressesSearchProps {
|
||||||
|
type: AddressTypeEnum;
|
||||||
|
selectedCustomerAddressId?: string;
|
||||||
|
customerAddresses: CustomerAddresses_user_addresses[];
|
||||||
|
onChangeCustomerShippingAddress: (
|
||||||
|
customerAddress: CustomerAddresses_user_addresses
|
||||||
|
) => void;
|
||||||
|
onChangeCustomerBillingAddress: (
|
||||||
|
customerAddress: CustomerAddresses_user_addresses
|
||||||
|
) => void;
|
||||||
|
exitSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderCustomerAddressesSearch: React.FC<OrderCustomerAddressesSearchProps> = props => {
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
selectedCustomerAddressId,
|
||||||
|
customerAddresses,
|
||||||
|
onChangeCustomerShippingAddress,
|
||||||
|
onChangeCustomerBillingAddress,
|
||||||
|
exitSearch
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const intl = useIntl();
|
||||||
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
const initialAddress = customerAddresses.find(
|
||||||
|
getById(selectedCustomerAddressId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const [query, setQuery] = React.useState("");
|
||||||
|
const [
|
||||||
|
temporarySelectedAddress,
|
||||||
|
setTemporarySelectedAddress
|
||||||
|
] = React.useState(initialAddress);
|
||||||
|
|
||||||
|
const handleSelect = () => {
|
||||||
|
if (type === AddressTypeEnum.SHIPPING) {
|
||||||
|
onChangeCustomerShippingAddress(temporarySelectedAddress);
|
||||||
|
} else {
|
||||||
|
onChangeCustomerBillingAddress(temporarySelectedAddress);
|
||||||
|
}
|
||||||
|
exitSearch();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setQuery(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredCustomerAddresses = customerAddresses.filter(address => {
|
||||||
|
const parsedAddress = stringifyAddress(address);
|
||||||
|
|
||||||
|
return parsedAddress.search(new RegExp(parseQuery(query), "i")) >= 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DialogTitle>
|
||||||
|
{type === AddressTypeEnum.SHIPPING ? (
|
||||||
|
<FormattedMessage {...messages.shippingTitle} />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage {...messages.billingTitle} />
|
||||||
|
)}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
{intl.formatMessage(messages.searchInfo)}
|
||||||
|
<CardSpacer />
|
||||||
|
<TextField
|
||||||
|
value={query}
|
||||||
|
variant="outlined"
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder={"Search addresses"}
|
||||||
|
fullWidth
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<SearchIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
inputProps={{ className: classes.searchInput }}
|
||||||
|
/>
|
||||||
|
<CardSpacer />
|
||||||
|
<div className={classes.scrollableWrapper}>
|
||||||
|
{filteredCustomerAddresses.length === 0
|
||||||
|
? intl.formatMessage(messages.noResultsFound)
|
||||||
|
: filteredCustomerAddresses?.map(address => (
|
||||||
|
<React.Fragment key={address.id}>
|
||||||
|
<CustomerAddressChoiceCard
|
||||||
|
selected={address.id === temporarySelectedAddress.id}
|
||||||
|
onSelect={() => setTemporarySelectedAddress(address)}
|
||||||
|
address={address}
|
||||||
|
/>
|
||||||
|
<CardSpacer />
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => exitSearch()} color="primary">
|
||||||
|
<FormattedMessage {...buttonMessages.cancel} />
|
||||||
|
</Button>
|
||||||
|
<ConfirmButton
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
transitionState="default"
|
||||||
|
onClick={handleSelect}
|
||||||
|
>
|
||||||
|
<FormattedMessage {...buttonMessages.select} />
|
||||||
|
</ConfirmButton>
|
||||||
|
</DialogActions>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
OrderCustomerAddressesSearch.displayName = "OrderCustomerAddressesSearch";
|
||||||
|
export default OrderCustomerAddressesSearch;
|
|
@ -2,11 +2,11 @@ import { defineMessages } from "react-intl";
|
||||||
|
|
||||||
export const dialogMessages = defineMessages({
|
export const dialogMessages = defineMessages({
|
||||||
title: {
|
title: {
|
||||||
defaultMessage: "Shipping address for order",
|
defaultMessage: "Change address for order",
|
||||||
description: "dialog header"
|
description: "dialog header"
|
||||||
},
|
},
|
||||||
billingSameAsShipping: {
|
billingSameAsShipping: {
|
||||||
defaultMessage: "Billing address same as shipping address",
|
defaultMessage: "Set the same for billing address",
|
||||||
description: "checkbox label"
|
description: "checkbox label"
|
||||||
},
|
},
|
||||||
shippingAddressDescription: {
|
shippingAddressDescription: {
|
||||||
|
@ -39,3 +39,21 @@ export const addressEditMessages = defineMessages({
|
||||||
description: "address type"
|
description: "address type"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const addressSearchMessages = defineMessages({
|
||||||
|
shippingTitle: {
|
||||||
|
defaultMessage: "Shipping address",
|
||||||
|
description: "search modal shipping title"
|
||||||
|
},
|
||||||
|
billingTitle: {
|
||||||
|
defaultMessage: "Billing address",
|
||||||
|
description: "search modal billing title"
|
||||||
|
},
|
||||||
|
searchInfo: {
|
||||||
|
defaultMessage: "Select an address you want to use from the list below",
|
||||||
|
description: "modal information under title"
|
||||||
|
},
|
||||||
|
noResultsFound: {
|
||||||
|
defaultMessage: "No results found"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
|
|
||||||
export const useStyles = makeStyles(
|
export const useStyles = makeStyles(
|
||||||
{
|
theme => ({
|
||||||
scrollableContent: {
|
scrollableContent: {
|
||||||
maxHeight: `calc(100vh - 250px)`,
|
maxHeight: `calc(100vh - 250px)`,
|
||||||
overflow: "scroll"
|
overflowY: "scroll",
|
||||||
|
overflowX: "hidden"
|
||||||
},
|
},
|
||||||
scrollableWrapper: {
|
scrollableWrapper: {
|
||||||
maxHeight: 400,
|
maxHeight: 400,
|
||||||
overflow: "scroll"
|
overflowY: "scroll"
|
||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
display: "block"
|
display: "block"
|
||||||
|
@ -18,7 +19,11 @@ export const useStyles = makeStyles(
|
||||||
},
|
},
|
||||||
overflow: {
|
overflow: {
|
||||||
overflowY: "visible"
|
overflowY: "visible"
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
searchInput: {
|
||||||
|
paddingTop: theme.spacing(2),
|
||||||
|
paddingBottom: theme.spacing(2)
|
||||||
|
}
|
||||||
|
}),
|
||||||
{ name: "OrderCustomerAddressesEditDialog" }
|
{ name: "OrderCustomerAddressesEditDialog" }
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import {
|
||||||
|
CustomerAddresses_user_addresses,
|
||||||
|
CustomerAddresses_user_defaultShippingAddress
|
||||||
|
} from "@saleor/customers/types/CustomerAddresses";
|
||||||
|
import { flatten } from "@saleor/misc";
|
||||||
|
|
||||||
|
import { getById } from "../OrderReturnPage/utils";
|
||||||
|
|
||||||
|
export const stringifyAddress = (
|
||||||
|
address: Partial<CustomerAddresses_user_addresses>
|
||||||
|
): string => {
|
||||||
|
const { id, ...addressWithoutId } = address;
|
||||||
|
return Object.values(flatten(addressWithoutId)).join(" ");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseQuery = (query: string) =>
|
||||||
|
query.replace(/([.?*+\-=:^$\\[\]<>(){}|])/g, "\\$&");
|
||||||
|
|
||||||
|
export function validateDefaultAddress<
|
||||||
|
T extends CustomerAddresses_user_defaultShippingAddress
|
||||||
|
>(
|
||||||
|
defaultAddress: CustomerAddresses_user_defaultShippingAddress,
|
||||||
|
customerAddresses: T[]
|
||||||
|
): CustomerAddresses_user_defaultShippingAddress {
|
||||||
|
const fallbackAddress = {
|
||||||
|
id: customerAddresses[0]?.id
|
||||||
|
} as CustomerAddresses_user_defaultShippingAddress;
|
||||||
|
if (!defaultAddress) {
|
||||||
|
return fallbackAddress;
|
||||||
|
}
|
||||||
|
if (!customerAddresses.some(getById(defaultAddress.id))) {
|
||||||
|
return fallbackAddress;
|
||||||
|
}
|
||||||
|
return defaultAddress;
|
||||||
|
}
|
Loading…
Reference in a new issue