* 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": {
|
||||
"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": {
|
||||
"context": "address type",
|
||||
|
@ -4065,13 +4069,24 @@
|
|||
"context": "address type",
|
||||
"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": {
|
||||
"context": "dialog content",
|
||||
"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": {
|
||||
"context": "dialog header",
|
||||
"string": "Shipping address for order"
|
||||
"string": "Change address for order"
|
||||
},
|
||||
"src_dot_orders_dot_components_dot_OrderCustomerChangeDialog_dot_changeAddress": {
|
||||
"context": "option label",
|
||||
|
@ -6748,6 +6763,9 @@
|
|||
"context": "select all options, button",
|
||||
"string": "Select All"
|
||||
},
|
||||
"src_dot_selected": {
|
||||
"string": "Selected"
|
||||
},
|
||||
"src_dot_send": {
|
||||
"context": "button",
|
||||
"string": "Send"
|
||||
|
|
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -5166,9 +5166,9 @@
|
|||
}
|
||||
},
|
||||
"@saleor/macaw-ui": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.2.6.tgz",
|
||||
"integrity": "sha512-ySEhWN9kyxX+5ATXVOg4siS5RwMRIMcLAybJHax0SpzS4E6FtLM5VDQhWliHaM2hicFPMVrLBa7MkTvAV1JcIA==",
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.2.7.tgz",
|
||||
"integrity": "sha512-sRt193W5u1Vu+5893zEsRbkVF3H1uXuqNC+BEXwTg85i+J9YHmykJZjDneExgGH24nYe948S5dffsy3NMOGSTQ==",
|
||||
"requires": {
|
||||
"clsx": "^1.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.58",
|
||||
"@material-ui/styles": "^4.11.4",
|
||||
"@saleor/macaw-ui": "^0.2.6",
|
||||
"@saleor/macaw-ui": "^0.2.7",
|
||||
"@sentry/react": "^6.0.0",
|
||||
"@types/faker": "^5.1.6",
|
||||
"@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 { commonMessages } from "@saleor/intl";
|
||||
import { EditIcon } from "@saleor/macaw-ui";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { CustomerAddresses_user_addresses } from "../../types/CustomerAddresses";
|
||||
import { useStyles } from "./styles";
|
||||
|
||||
export interface CustomerAddressChoiceCardProps {
|
||||
address: CustomerAddresses_user_addresses;
|
||||
selected: boolean;
|
||||
onSelect: () => void;
|
||||
selected?: boolean;
|
||||
editable?: boolean;
|
||||
onSelect?: () => void;
|
||||
onEditClick?: () => void;
|
||||
}
|
||||
|
||||
const CustomerAddressChoiceCard: React.FC<CustomerAddressChoiceCardProps> = props => {
|
||||
const { address, selected, onSelect } = props;
|
||||
const { address, selected, editable, onSelect, onEditClick } = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={classNames(classes.card, {
|
||||
[classes.cardSelected]: selected
|
||||
[classes.cardSelected]: selected,
|
||||
[classes.selectableCard]: !editable
|
||||
})}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<CardContent>
|
||||
<CardContent className={classes.cardContent}>
|
||||
<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>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -3,13 +3,37 @@ import { makeStyles } from "@saleor/macaw-ui";
|
|||
export const useStyles = makeStyles(
|
||||
theme => ({
|
||||
card: {
|
||||
cursor: "pointer",
|
||||
padding: "1px"
|
||||
},
|
||||
cardSelected: {
|
||||
borderColor: theme.palette.primary.main,
|
||||
borderWidth: "2px",
|
||||
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" }
|
||||
|
|
|
@ -84,6 +84,9 @@ export const commonMessages = defineMessages({
|
|||
savedChanges: {
|
||||
defaultMessage: "Saved changes"
|
||||
},
|
||||
selected: {
|
||||
defaultMessage: "Selected"
|
||||
},
|
||||
sessionExpired: {
|
||||
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}`;
|
||||
}
|
||||
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 { useIntl } from "react-intl";
|
||||
|
||||
import { getById } from "../OrderReturnPage/utils";
|
||||
import { AddressInputOptionEnum } from "./form";
|
||||
import { addressEditMessages } from "./messages";
|
||||
import { useStyles } from "./styles";
|
||||
|
@ -24,15 +25,13 @@ export interface OrderCustomerAddressEditProps {
|
|||
addressInputOption: AddressInputOptionEnum;
|
||||
addressInputName: string;
|
||||
onChangeAddressInputOption: FormChange;
|
||||
customerAddressId: string;
|
||||
selectedCustomerAddressId: string;
|
||||
formAddress: AddressTypeInput;
|
||||
formAddressCountryDisplayName: string;
|
||||
formErrors: Array<AccountErrorFragment | OrderErrorFragment>;
|
||||
onChangeCustomerAddress: (
|
||||
customerAddress: CustomerAddresses_user_addresses
|
||||
) => void;
|
||||
onChangeFormAddress: (event: React.ChangeEvent<any>) => void;
|
||||
onChangeFormAddressCountry: (event: React.ChangeEvent<any>) => void;
|
||||
onEdit?: () => void;
|
||||
}
|
||||
|
||||
const OrderCustomerAddressEdit: React.FC<OrderCustomerAddressEditProps> = props => {
|
||||
|
@ -43,13 +42,13 @@ const OrderCustomerAddressEdit: React.FC<OrderCustomerAddressEditProps> = props
|
|||
addressInputOption,
|
||||
addressInputName,
|
||||
onChangeAddressInputOption,
|
||||
customerAddressId,
|
||||
selectedCustomerAddressId,
|
||||
formAddress,
|
||||
formAddressCountryDisplayName,
|
||||
formErrors,
|
||||
onChangeCustomerAddress,
|
||||
onChangeFormAddress,
|
||||
onChangeFormAddressCountry
|
||||
onChangeFormAddressCountry,
|
||||
onEdit
|
||||
} = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
|
@ -92,19 +91,15 @@ const OrderCustomerAddressEdit: React.FC<OrderCustomerAddressEditProps> = props
|
|||
className={classes.optionLabel}
|
||||
/>
|
||||
{addressInputOption === AddressInputOptionEnum.CUSTOMER_ADDRESS && (
|
||||
<div className={classes.scrollableWrapper}>
|
||||
{customerAddresses.map(customerAddress => (
|
||||
<React.Fragment key={customerAddress.id}>
|
||||
<CardSpacer />
|
||||
<CustomerAddressChoiceCard
|
||||
address={customerAddress}
|
||||
selected={customerAddress.id === customerAddressId}
|
||||
onSelect={() => onChangeCustomerAddress(customerAddress)}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
<>
|
||||
<CardSpacer />
|
||||
<CustomerAddressChoiceCard
|
||||
address={customerAddresses.find(getById(selectedCustomerAddressId))}
|
||||
editable
|
||||
onEditClick={onEdit}
|
||||
/>
|
||||
<FormSpacer />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<FormControlLabel
|
||||
value={AddressInputOptionEnum.NEW_ADDRESS}
|
||||
|
|
|
@ -36,8 +36,14 @@ import OrderCustomerAddressesEditForm, {
|
|||
} from "./form";
|
||||
import { dialogMessages } from "./messages";
|
||||
import OrderCustomerAddressEdit from "./OrderCustomerAddressEdit";
|
||||
import OrderCustomerAddressesSearch from "./OrderCustomerAddressesSearch";
|
||||
import { useStyles } from "./styles";
|
||||
import { validateDefaultAddress } from "./utils";
|
||||
|
||||
export interface OrderCustomerSearchAddressState {
|
||||
open: boolean;
|
||||
type: AddressTypeEnum;
|
||||
}
|
||||
export interface OrderCustomerAddressesEditDialogOutput {
|
||||
shippingAddress: AddressInput;
|
||||
billingAddress: AddressInput;
|
||||
|
@ -56,6 +62,11 @@ export interface OrderCustomerAddressesEditDialogProps {
|
|||
onConfirm(data: OrderCustomerAddressesEditDialogOutput): SubmitPromise;
|
||||
}
|
||||
|
||||
const defaultSearchState: OrderCustomerSearchAddressState = {
|
||||
open: false,
|
||||
type: undefined
|
||||
};
|
||||
|
||||
const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialogProps> = props => {
|
||||
const {
|
||||
open,
|
||||
|
@ -85,9 +96,11 @@ const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialo
|
|||
open
|
||||
);
|
||||
|
||||
const getCustomerAddress = (customerAddressId: string): AddressInput =>
|
||||
const getCustomerAddress = (
|
||||
selectedCustomerAddressID: string
|
||||
): AddressInput =>
|
||||
transformAddressToAddressInput(
|
||||
customerAddresses.find(getById(customerAddressId))
|
||||
customerAddresses.find(getById(selectedCustomerAddressID))
|
||||
);
|
||||
|
||||
const handleAddressesSubmit = (data: OrderCustomerAddressesEditFormData) => {
|
||||
|
@ -127,88 +140,73 @@ const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialo
|
|||
|
||||
const countryChoices = mapCountriesToChoices(countries);
|
||||
|
||||
const [addressSearchState, setAddressSearchState] = React.useState<
|
||||
OrderCustomerSearchAddressState
|
||||
>(defaultSearchState);
|
||||
|
||||
const validatedDefaultShippingAddress = validateDefaultAddress(
|
||||
defaultShippingAddress,
|
||||
customerAddresses
|
||||
);
|
||||
const validatedDefaultBillingAddress = validateDefaultAddress(
|
||||
defaultBillingAddress,
|
||||
customerAddresses
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={open}>
|
||||
<Dialog
|
||||
onClose={() => {
|
||||
setAddressSearchState(defaultSearchState);
|
||||
onClose();
|
||||
}}
|
||||
open={open}
|
||||
fullWidth
|
||||
>
|
||||
<OrderCustomerAddressesEditForm
|
||||
countryChoices={countryChoices}
|
||||
defaultShippingAddress={defaultShippingAddress}
|
||||
defaultBillingAddress={defaultBillingAddress}
|
||||
defaultShippingAddress={validatedDefaultShippingAddress}
|
||||
defaultBillingAddress={validatedDefaultBillingAddress}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ change, data, handlers }) => (
|
||||
<>
|
||||
<DialogTitle>
|
||||
<FormattedMessage {...dialogMessages.title} />
|
||||
</DialogTitle>
|
||||
<DialogContent className={classes.scrollableContent}>
|
||||
<Typography>
|
||||
{customerAddresses.length > 0 ? (
|
||||
<FormattedMessage
|
||||
{...dialogMessages.customerShippingAddressDescription}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
{...dialogMessages.shippingAddressDescription}
|
||||
/>
|
||||
)}
|
||||
</Typography>
|
||||
<FormSpacer />
|
||||
<OrderCustomerAddressEdit
|
||||
loading={loading}
|
||||
countryChoices={countryChoices}
|
||||
addressInputOption={data.shippingAddressInputOption}
|
||||
addressInputName="shippingAddressInputOption"
|
||||
onChangeAddressInputOption={change}
|
||||
{addressSearchState.open ? (
|
||||
<OrderCustomerAddressesSearch
|
||||
type={addressSearchState?.type}
|
||||
customerAddresses={customerAddresses}
|
||||
customerAddressId={data.customerShippingAddress?.id}
|
||||
formAddress={data.shippingAddress}
|
||||
formAddressCountryDisplayName={data.shippingCountryDisplayName}
|
||||
formErrors={dialogErrors.filter(
|
||||
error => error.addressType === AddressTypeEnum.SHIPPING
|
||||
)}
|
||||
onChangeCustomerAddress={customerAddress =>
|
||||
selectedCustomerAddressId={
|
||||
addressSearchState.type === AddressTypeEnum.SHIPPING
|
||||
? data.customerShippingAddress?.id
|
||||
: data.customerBillingAddress?.id
|
||||
}
|
||||
onChangeCustomerShippingAddress={customerAddress =>
|
||||
handlers.changeCustomerAddress(
|
||||
customerAddress,
|
||||
"customerShippingAddress"
|
||||
)
|
||||
}
|
||||
onChangeFormAddress={event =>
|
||||
handlers.changeFormAddress(event, "shippingAddress")
|
||||
onChangeCustomerBillingAddress={customerAddress =>
|
||||
handlers.changeCustomerAddress(
|
||||
customerAddress,
|
||||
"customerBillingAddress"
|
||||
)
|
||||
}
|
||||
onChangeFormAddressCountry={handlers.selectShippingCountry}
|
||||
exitSearch={() => setAddressSearchState(defaultSearchState)}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<Divider />
|
||||
<FormSpacer />
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.billingSameAsShipping}
|
||||
name="billingSameAsShipping"
|
||||
onChange={() =>
|
||||
change({
|
||||
target: {
|
||||
name: "billingSameAsShipping",
|
||||
value: !data.billingSameAsShipping
|
||||
}
|
||||
})
|
||||
}
|
||||
data-test="billingSameAsShipping"
|
||||
/>
|
||||
}
|
||||
label={intl.formatMessage(dialogMessages.billingSameAsShipping)}
|
||||
/>
|
||||
{!data.billingSameAsShipping && (
|
||||
<>
|
||||
<FormSpacer />
|
||||
) : (
|
||||
<>
|
||||
<DialogTitle>
|
||||
<FormattedMessage {...dialogMessages.title} />
|
||||
</DialogTitle>
|
||||
<DialogContent className={classes.scrollableContent}>
|
||||
<Typography>
|
||||
{customerAddresses.length > 0 ? (
|
||||
<FormattedMessage
|
||||
{...dialogMessages.customerBillingAddressDescription}
|
||||
{...dialogMessages.customerShippingAddressDescription}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
{...dialogMessages.billingAddressDescription}
|
||||
{...dialogMessages.shippingAddressDescription}
|
||||
/>
|
||||
)}
|
||||
</Typography>
|
||||
|
@ -216,43 +214,113 @@ const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialo
|
|||
<OrderCustomerAddressEdit
|
||||
loading={loading}
|
||||
countryChoices={countryChoices}
|
||||
addressInputOption={data.billingAddressInputOption}
|
||||
addressInputName="billingAddressInputOption"
|
||||
addressInputOption={data.shippingAddressInputOption}
|
||||
addressInputName="shippingAddressInputOption"
|
||||
onChangeAddressInputOption={change}
|
||||
customerAddresses={customerAddresses}
|
||||
customerAddressId={data.customerBillingAddress?.id}
|
||||
formAddress={data.billingAddress}
|
||||
selectedCustomerAddressId={data.customerShippingAddress?.id}
|
||||
formAddress={data.shippingAddress}
|
||||
formAddressCountryDisplayName={
|
||||
data.billingCountryDisplayName
|
||||
data.shippingCountryDisplayName
|
||||
}
|
||||
formErrors={dialogErrors.filter(
|
||||
error => error.addressType === AddressTypeEnum.BILLING
|
||||
error => error.addressType === AddressTypeEnum.SHIPPING
|
||||
)}
|
||||
onChangeCustomerAddress={customerAddress =>
|
||||
handlers.changeCustomerAddress(
|
||||
customerAddress,
|
||||
"customerBillingAddress"
|
||||
)
|
||||
}
|
||||
onChangeFormAddress={event =>
|
||||
handlers.changeFormAddress(event, "billingAddress")
|
||||
handlers.changeFormAddress(event, "shippingAddress")
|
||||
}
|
||||
onChangeFormAddressCountry={handlers.selectShippingCountry}
|
||||
onEdit={() =>
|
||||
setAddressSearchState({
|
||||
open: true,
|
||||
type: AddressTypeEnum.SHIPPING
|
||||
})
|
||||
}
|
||||
onChangeFormAddressCountry={handlers.selectBillingCountry}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<ConfirmButton
|
||||
transitionState={confirmButtonState}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
type="submit"
|
||||
data-test="submit"
|
||||
>
|
||||
<FormattedMessage {...buttonMessages.select} />
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
<FormSpacer />
|
||||
<Divider />
|
||||
<FormSpacer />
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data.billingSameAsShipping}
|
||||
name="billingSameAsShipping"
|
||||
onChange={() =>
|
||||
change({
|
||||
target: {
|
||||
name: "billingSameAsShipping",
|
||||
value: !data.billingSameAsShipping
|
||||
}
|
||||
})
|
||||
}
|
||||
data-test="billingSameAsShipping"
|
||||
/>
|
||||
}
|
||||
label={intl.formatMessage(
|
||||
dialogMessages.billingSameAsShipping
|
||||
)}
|
||||
/>
|
||||
{!data.billingSameAsShipping && (
|
||||
<>
|
||||
<FormSpacer />
|
||||
<Typography>
|
||||
{customerAddresses.length > 0 ? (
|
||||
<FormattedMessage
|
||||
{...dialogMessages.customerBillingAddressDescription}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
{...dialogMessages.billingAddressDescription}
|
||||
/>
|
||||
)}
|
||||
</Typography>
|
||||
<FormSpacer />
|
||||
<OrderCustomerAddressEdit
|
||||
loading={loading}
|
||||
countryChoices={countryChoices}
|
||||
addressInputOption={data.billingAddressInputOption}
|
||||
addressInputName="billingAddressInputOption"
|
||||
onChangeAddressInputOption={change}
|
||||
customerAddresses={customerAddresses}
|
||||
selectedCustomerAddressId={
|
||||
data.customerBillingAddress?.id
|
||||
}
|
||||
formAddress={data.billingAddress}
|
||||
formAddressCountryDisplayName={
|
||||
data.billingCountryDisplayName
|
||||
}
|
||||
formErrors={dialogErrors.filter(
|
||||
error => error.addressType === AddressTypeEnum.BILLING
|
||||
)}
|
||||
onChangeFormAddress={event =>
|
||||
handlers.changeFormAddress(event, "billingAddress")
|
||||
}
|
||||
onChangeFormAddressCountry={
|
||||
handlers.selectBillingCountry
|
||||
}
|
||||
onEdit={() =>
|
||||
setAddressSearchState({
|
||||
open: true,
|
||||
type: AddressTypeEnum.BILLING
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<ConfirmButton
|
||||
transitionState={confirmButtonState}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
type="submit"
|
||||
data-test="submit"
|
||||
>
|
||||
<FormattedMessage {...buttonMessages.save} />
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</OrderCustomerAddressesEditForm>
|
||||
|
|
|
@ -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({
|
||||
title: {
|
||||
defaultMessage: "Shipping address for order",
|
||||
defaultMessage: "Change address for order",
|
||||
description: "dialog header"
|
||||
},
|
||||
billingSameAsShipping: {
|
||||
defaultMessage: "Billing address same as shipping address",
|
||||
defaultMessage: "Set the same for billing address",
|
||||
description: "checkbox label"
|
||||
},
|
||||
shippingAddressDescription: {
|
||||
|
@ -39,3 +39,21 @@ export const addressEditMessages = defineMessages({
|
|||
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";
|
||||
|
||||
export const useStyles = makeStyles(
|
||||
{
|
||||
theme => ({
|
||||
scrollableContent: {
|
||||
maxHeight: `calc(100vh - 250px)`,
|
||||
overflow: "scroll"
|
||||
overflowY: "scroll",
|
||||
overflowX: "hidden"
|
||||
},
|
||||
scrollableWrapper: {
|
||||
maxHeight: 400,
|
||||
overflow: "scroll"
|
||||
overflowY: "scroll"
|
||||
},
|
||||
container: {
|
||||
display: "block"
|
||||
|
@ -18,7 +19,11 @@ export const useStyles = makeStyles(
|
|||
},
|
||||
overflow: {
|
||||
overflowY: "visible"
|
||||
},
|
||||
searchInput: {
|
||||
paddingTop: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(2)
|
||||
}
|
||||
},
|
||||
}),
|
||||
{ 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