feat: add country select (#410)

* feat:  add country-select

* feat:  add taxjar address-factory

* feat:  add validateAddress methods to [provider]-client

* build: 👷 add changeset
This commit is contained in:
Adrian Pilarczyk 2023-04-21 07:55:43 +02:00 committed by GitHub
parent ede7a2e808
commit 84e9ca5d66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 557 additions and 12 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-app-taxes": minor
---
Add `<CountrySelect />` and use it in the `channel-tax-provider-form.ts` to ensure the correct formatting of the country code.

View file

@ -6,6 +6,7 @@ import { createLogger } from "../../lib/logger";
import { AvataxConfig } from "./avatax-config";
import { CommitTransactionModel } from "avatax/lib/models/CommitTransactionModel";
import { DocumentType } from "avatax/lib/enums/DocumentType";
import { AddressLocationInfo as AvataxAddress } from "avatax/lib/models/AddressLocationInfo";
type AvataxSettings = {
appName: string;
@ -48,6 +49,10 @@ export type CreateTransactionArgs = {
model: CreateTransactionModel;
};
export type ValidateAddressArgs = {
address: AvataxAddress;
};
export class AvataxClient {
private client: Avatax;
private logger: pino.Logger;
@ -97,4 +102,10 @@ export class AvataxClient {
};
}
}
async validateAddress({ address }: ValidateAddressArgs) {
this.logger.debug({ address }, "validateAddress called with:");
return this.client.resolveAddress(address);
}
}

View file

@ -1,8 +1,6 @@
import { AddressLocationInfo as AvataxAddress } from "avatax/lib/models/AddressLocationInfo";
import { ChannelAddress } from "../../channels-configuration/channels-config";
import { AddressFragment } from "../../../../generated/graphql";
type SaleorAddress = AddressFragment;
import { AddressFragment as SaleorAddress } from "../../../../generated/graphql";
function mapSaleorAddressToAvataxAddress(address: SaleorAddress): AvataxAddress {
return {

View file

@ -13,6 +13,7 @@ import {
import { Save } from "@material-ui/icons";
import { Button, makeStyles } from "@saleor/macaw-ui";
import { useDashboardNotification } from "@saleor/apps-shared";
import React from "react";
import { Controller, useForm } from "react-hook-form";
import {
@ -24,7 +25,7 @@ import { ProvidersConfig } from "../../providers-configuration/providers-config"
import { ProviderIcon } from "../../providers-configuration/ui/provider-icon";
import { useChannelSlug } from "../../taxes/tax-context";
import { trpcClient } from "../../trpc/trpc-client";
import { useDashboardNotification } from "@saleor/apps-shared";
import { CountrySelect } from "../../ui/country-select/country-select";
type ChannelTaxProviderFormValues = ChannelConfig;
@ -67,7 +68,6 @@ const getDefaultFormValues = (
return defaultChannelConfig;
};
// todo: rename because address is here
export const ChannelTaxProviderForm = () => {
const styles = useStyles();
const { control, reset, handleSubmit } = useForm<ChannelTaxProviderFormValues>({
@ -158,14 +158,10 @@ export const ChannelTaxProviderForm = () => {
<Grid container spacing={2}>
<Grid item xs={8}>
{/* // todo: add country select */}
<Controller
name="address.country"
control={control}
defaultValue=""
render={({ field }) => (
<TextField {...field} label="Country" {...textFieldProps} />
)}
render={({ field }) => <CountrySelect {...field} />}
/>
</Grid>
<Grid item xs={4}>

View file

@ -0,0 +1,68 @@
import { describe, expect, it } from "vitest";
import { taxJarAddressFactory } from "./address-factory";
describe("taxJarAddressFactory", () => {
describe("fromChannelAddress", () => {
it("returns fields in the expected format", () => {
const result = taxJarAddressFactory.fromChannelAddress({
city: "LOS ANGELES",
country: "US",
state: "CA",
street: "123 Palm Grove Ln",
zip: "90002",
});
expect(result).toEqual({
street: "123 Palm Grove Ln",
city: "LOS ANGELES",
state: "CA",
zip: "90002",
country: "US",
});
});
});
describe("fromSaleorAddress", () => {
it("returns fields in the expected format with streetAddress1", () => {
const result = taxJarAddressFactory.fromSaleorAddress({
streetAddress1: "123 Palm Grove Ln",
streetAddress2: "",
city: "LOS ANGELES",
country: {
code: "US",
},
countryArea: "CA",
postalCode: "90002",
});
expect(result).toEqual({
street: "123 Palm Grove Ln",
city: "LOS ANGELES",
state: "CA",
zip: "90002",
country: "US",
});
});
it("returns fields in the expected format with streetAddress1 and streetAddress2", () => {
const result = taxJarAddressFactory.fromSaleorAddress({
streetAddress1: "123 Palm",
streetAddress2: "Grove Ln",
city: "LOS ANGELES",
country: {
code: "US",
},
countryArea: "CA",
postalCode: "90002",
});
expect(result).toEqual({
street: "123 Palm Grove Ln",
city: "LOS ANGELES",
state: "CA",
zip: "90002",
country: "US",
});
});
});
});

View file

@ -0,0 +1,32 @@
import { ChannelAddress } from "../../channels-configuration/channels-config";
import { AddressFragment as SaleorAddress } from "../../../../generated/graphql";
import { AddressParams as TaxJarAddress } from "taxjar/dist/types/paramTypes";
function joinAddresses(address1: string, address2: string): string {
return `${address1}${address2.length > 0 ? " " + address2 : ""}`;
}
function mapSaleorAddressToTaxJarAddress(address: SaleorAddress): TaxJarAddress {
return {
street: joinAddresses(address.streetAddress1, address.streetAddress2),
city: address.city,
zip: address.postalCode,
state: address.countryArea,
country: address.country.code,
};
}
function mapChannelAddressToTaxJarAddress(address: ChannelAddress): TaxJarAddress {
return {
city: address.city,
country: address.country,
state: address.state,
street: address.street,
zip: address.zip,
};
}
export const taxJarAddressFactory = {
fromSaleorAddress: mapSaleorAddressToTaxJarAddress,
fromChannelAddress: mapChannelAddressToTaxJarAddress,
};

View file

@ -1,6 +1,6 @@
import pino from "pino";
import TaxJar from "taxjar";
import { Config, CreateOrderParams, TaxParams } from "taxjar/dist/util/types";
import { AddressParams, Config, CreateOrderParams, TaxParams } from "taxjar/dist/util/types";
import { createLogger } from "../../lib/logger";
import { TaxJarConfig } from "./taxjar-config";
@ -21,6 +21,10 @@ export type CreateOrderArgs = {
params: CreateOrderParams;
};
export type ValidateAddressArgs = {
params: AddressParams;
};
export class TaxJarClient {
private client: TaxJar;
private logger: pino.Logger;
@ -56,8 +60,14 @@ export class TaxJarClient {
}
async createOrder({ params }: CreateOrderArgs) {
this.logger.debug("createOrder called with:");
this.logger.debug({ params }, "createOrder called with:");
return this.client.createOrder(params);
}
async validateAddress({ params }: ValidateAddressArgs) {
this.logger.debug({ params }, "validateAddress called with:");
return this.client.validateAddress(params);
}
}

View file

@ -0,0 +1,382 @@
type CountryType = {
code: string;
label: string;
};
// From https://bitbucket.org/atlassian/atlaskit-mk-2/raw/4ad0e56649c3e6c973e226b7efaeb28cb240ccb0/packages/core/select/src/data/countries.js
export const countries: CountryType[] = [
{ code: "AD", label: "Andorra" },
{
code: "AE",
label: "United Arab Emirates",
},
{ code: "AF", label: "Afghanistan" },
{
code: "AG",
label: "Antigua and Barbuda",
},
{ code: "AI", label: "Anguilla" },
{ code: "AL", label: "Albania" },
{ code: "AM", label: "Armenia" },
{ code: "AO", label: "Angola" },
{ code: "AQ", label: "Antarctica" },
{ code: "AR", label: "Argentina" },
{ code: "AS", label: "American Samoa" },
{ code: "AT", label: "Austria" },
{
code: "AU",
label: "Australia",
},
{ code: "AW", label: "Aruba" },
{ code: "AX", label: "Alland Islands" },
{ code: "AZ", label: "Azerbaijan" },
{
code: "BA",
label: "Bosnia and Herzegovina",
},
{ code: "BB", label: "Barbados" },
{ code: "BD", label: "Bangladesh" },
{ code: "BE", label: "Belgium" },
{ code: "BF", label: "Burkina Faso" },
{ code: "BG", label: "Bulgaria" },
{ code: "BH", label: "Bahrain" },
{ code: "BI", label: "Burundi" },
{ code: "BJ", label: "Benin" },
{ code: "BL", label: "Saint Barthelemy" },
{ code: "BM", label: "Bermuda" },
{ code: "BN", label: "Brunei Darussalam" },
{ code: "BO", label: "Bolivia" },
{ code: "BR", label: "Brazil" },
{ code: "BS", label: "Bahamas" },
{ code: "BT", label: "Bhutan" },
{ code: "BV", label: "Bouvet Island" },
{ code: "BW", label: "Botswana" },
{ code: "BY", label: "Belarus" },
{ code: "BZ", label: "Belize" },
{
code: "CA",
label: "Canada",
},
{
code: "CC",
label: "Cocos (Keeling) Islands",
},
{
code: "CD",
label: "Congo, Democratic Republic of the",
},
{
code: "CF",
label: "Central African Republic",
},
{
code: "CG",
label: "Congo, Republic of the",
},
{ code: "CH", label: "Switzerland" },
{ code: "CI", label: "Cote d'Ivoire" },
{ code: "CK", label: "Cook Islands" },
{ code: "CL", label: "Chile" },
{ code: "CM", label: "Cameroon" },
{ code: "CN", label: "China" },
{ code: "CO", label: "Colombia" },
{ code: "CR", label: "Costa Rica" },
{ code: "CU", label: "Cuba" },
{ code: "CV", label: "Cape Verde" },
{ code: "CW", label: "Curacao" },
{ code: "CX", label: "Christmas Island" },
{ code: "CY", label: "Cyprus" },
{ code: "CZ", label: "Czech Republic" },
{
code: "DE",
label: "Germany",
},
{ code: "DJ", label: "Djibouti" },
{ code: "DK", label: "Denmark" },
{ code: "DM", label: "Dominica" },
{
code: "DO",
label: "Dominican Republic",
},
{ code: "DZ", label: "Algeria" },
{ code: "EC", label: "Ecuador" },
{ code: "EE", label: "Estonia" },
{ code: "EG", label: "Egypt" },
{ code: "EH", label: "Western Sahara" },
{ code: "ER", label: "Eritrea" },
{ code: "ES", label: "Spain" },
{ code: "ET", label: "Ethiopia" },
{ code: "FI", label: "Finland" },
{ code: "FJ", label: "Fiji" },
{
code: "FK",
label: "Falkland Islands (Malvinas)",
},
{
code: "FM",
label: "Micronesia, Federated States of",
},
{ code: "FO", label: "Faroe Islands" },
{
code: "FR",
label: "France",
},
{ code: "GA", label: "Gabon" },
{ code: "GB", label: "United Kingdom" },
{ code: "GD", label: "Grenada" },
{ code: "GE", label: "Georgia" },
{ code: "GF", label: "French Guiana" },
{ code: "GG", label: "Guernsey" },
{ code: "GH", label: "Ghana" },
{ code: "GI", label: "Gibraltar" },
{ code: "GL", label: "Greenland" },
{ code: "GM", label: "Gambia" },
{ code: "GN", label: "Guinea" },
{ code: "GP", label: "Guadeloupe" },
{ code: "GQ", label: "Equatorial Guinea" },
{ code: "GR", label: "Greece" },
{
code: "GS",
label: "South Georgia and the South Sandwich Islands",
},
{ code: "GT", label: "Guatemala" },
{ code: "GU", label: "Guam" },
{ code: "GW", label: "Guinea-Bissau" },
{ code: "GY", label: "Guyana" },
{ code: "HK", label: "Hong Kong" },
{
code: "HM",
label: "Heard Island and McDonald Islands",
},
{ code: "HN", label: "Honduras" },
{ code: "HR", label: "Croatia" },
{ code: "HT", label: "Haiti" },
{ code: "HU", label: "Hungary" },
{ code: "ID", label: "Indonesia" },
{ code: "IE", label: "Ireland" },
{ code: "IL", label: "Israel" },
{ code: "IM", label: "Isle of Man" },
{ code: "IN", label: "India" },
{
code: "IO",
label: "British Indian Ocean Territory",
},
{ code: "IQ", label: "Iraq" },
{
code: "IR",
label: "Iran, Islamic Republic of",
},
{ code: "IS", label: "Iceland" },
{ code: "IT", label: "Italy" },
{ code: "JE", label: "Jersey" },
{ code: "JM", label: "Jamaica" },
{ code: "JO", label: "Jordan" },
{
code: "JP",
label: "Japan",
},
{ code: "KE", label: "Kenya" },
{ code: "KG", label: "Kyrgyzstan" },
{ code: "KH", label: "Cambodia" },
{ code: "KI", label: "Kiribati" },
{ code: "KM", label: "Comoros" },
{
code: "KN",
label: "Saint Kitts and Nevis",
},
{
code: "KP",
label: "Korea, Democratic People's Republic of",
},
{ code: "KR", label: "Korea, Republic of" },
{ code: "KW", label: "Kuwait" },
{ code: "KY", label: "Cayman Islands" },
{ code: "KZ", label: "Kazakhstan" },
{
code: "LA",
label: "Lao People's Democratic Republic",
},
{ code: "LB", label: "Lebanon" },
{ code: "LC", label: "Saint Lucia" },
{ code: "LI", label: "Liechtenstein" },
{ code: "LK", label: "Sri Lanka" },
{ code: "LR", label: "Liberia" },
{ code: "LS", label: "Lesotho" },
{ code: "LT", label: "Lithuania" },
{ code: "LU", label: "Luxembourg" },
{ code: "LV", label: "Latvia" },
{ code: "LY", label: "Libya" },
{ code: "MA", label: "Morocco" },
{ code: "MC", label: "Monaco" },
{
code: "MD",
label: "Moldova, Republic of",
},
{ code: "ME", label: "Montenegro" },
{
code: "MF",
label: "Saint Martin (French part)",
},
{ code: "MG", label: "Madagascar" },
{ code: "MH", label: "Marshall Islands" },
{
code: "MK",
label: "Macedonia, the Former Yugoslav Republic of",
},
{ code: "ML", label: "Mali" },
{ code: "MM", label: "Myanmar" },
{ code: "MN", label: "Mongolia" },
{ code: "MO", label: "Macao" },
{
code: "MP",
label: "Northern Mariana Islands",
},
{ code: "MQ", label: "Martinique" },
{ code: "MR", label: "Mauritania" },
{ code: "MS", label: "Montserrat" },
{ code: "MT", label: "Malta" },
{ code: "MU", label: "Mauritius" },
{ code: "MV", label: "Maldives" },
{ code: "MW", label: "Malawi" },
{ code: "MX", label: "Mexico" },
{ code: "MY", label: "Malaysia" },
{ code: "MZ", label: "Mozambique" },
{ code: "NA", label: "Namibia" },
{ code: "NC", label: "New Caledonia" },
{ code: "NE", label: "Niger" },
{ code: "NF", label: "Norfolk Island" },
{ code: "NG", label: "Nigeria" },
{ code: "NI", label: "Nicaragua" },
{ code: "NL", label: "Netherlands" },
{ code: "NO", label: "Norway" },
{ code: "NP", label: "Nepal" },
{ code: "NR", label: "Nauru" },
{ code: "NU", label: "Niue" },
{ code: "NZ", label: "New Zealand" },
{ code: "OM", label: "Oman" },
{ code: "PA", label: "Panama" },
{ code: "PE", label: "Peru" },
{ code: "PF", label: "French Polynesia" },
{ code: "PG", label: "Papua New Guinea" },
{ code: "PH", label: "Philippines" },
{ code: "PK", label: "Pakistan" },
{ code: "PL", label: "Poland" },
{
code: "PM",
label: "Saint Pierre and Miquelon",
},
{ code: "PN", label: "Pitcairn" },
{ code: "PR", label: "Puerto Rico" },
{
code: "PS",
label: "Palestine, State of",
},
{ code: "PT", label: "Portugal" },
{ code: "PW", label: "Palau" },
{ code: "PY", label: "Paraguay" },
{ code: "QA", label: "Qatar" },
{ code: "RE", label: "Reunion" },
{ code: "RO", label: "Romania" },
{ code: "RS", label: "Serbia" },
{ code: "RU", label: "Russian Federation" },
{ code: "RW", label: "Rwanda" },
{ code: "SA", label: "Saudi Arabia" },
{ code: "SB", label: "Solomon Islands" },
{ code: "SC", label: "Seychelles" },
{ code: "SD", label: "Sudan" },
{ code: "SE", label: "Sweden" },
{ code: "SG", label: "Singapore" },
{ code: "SH", label: "Saint Helena" },
{ code: "SI", label: "Slovenia" },
{
code: "SJ",
label: "Svalbard and Jan Mayen",
},
{ code: "SK", label: "Slovakia" },
{ code: "SL", label: "Sierra Leone" },
{ code: "SM", label: "San Marino" },
{ code: "SN", label: "Senegal" },
{ code: "SO", label: "Somalia" },
{ code: "SR", label: "Suriname" },
{ code: "SS", label: "South Sudan" },
{
code: "ST",
label: "Sao Tome and Principe",
},
{ code: "SV", label: "El Salvador" },
{
code: "SX",
label: "Sint Maarten (Dutch part)",
},
{
code: "SY",
label: "Syrian Arab Republic",
},
{ code: "SZ", label: "Swaziland" },
{
code: "TC",
label: "Turks and Caicos Islands",
},
{ code: "TD", label: "Chad" },
{
code: "TF",
label: "French Southern Territories",
},
{ code: "TG", label: "Togo" },
{ code: "TH", label: "Thailand" },
{ code: "TJ", label: "Tajikistan" },
{ code: "TK", label: "Tokelau" },
{ code: "TL", label: "Timor-Leste" },
{ code: "TM", label: "Turkmenistan" },
{ code: "TN", label: "Tunisia" },
{ code: "TO", label: "Tonga" },
{ code: "TR", label: "Turkey" },
{
code: "TT",
label: "Trinidad and Tobago",
},
{ code: "TV", label: "Tuvalu" },
{
code: "TW",
label: "Taiwan, Province of China",
},
{
code: "TZ",
label: "United Republic of Tanzania",
},
{ code: "UA", label: "Ukraine" },
{ code: "UG", label: "Uganda" },
{
code: "US",
label: "United States",
},
{ code: "UY", label: "Uruguay" },
{ code: "UZ", label: "Uzbekistan" },
{
code: "VA",
label: "Holy See (Vatican City State)",
},
{
code: "VC",
label: "Saint Vincent and the Grenadines",
},
{ code: "VE", label: "Venezuela" },
{
code: "VG",
label: "British Virgin Islands",
},
{
code: "VI",
label: "US Virgin Islands",
},
{ code: "VN", label: "Vietnam" },
{ code: "VU", label: "Vanuatu" },
{ code: "WF", label: "Wallis and Futuna" },
{ code: "WS", label: "Samoa" },
{ code: "XK", label: "Kosovo" },
{ code: "YE", label: "Yemen" },
{ code: "YT", label: "Mayotte" },
{ code: "ZA", label: "South Africa" },
{ code: "ZM", label: "Zambia" },
{ code: "ZW", label: "Zimbabwe" },
];

View file

@ -0,0 +1,43 @@
import { TextField } from "@material-ui/core";
import { Autocomplete } from "@material-ui/lab";
import { makeStyles } from "@saleor/macaw-ui";
import { ControllerRenderProps } from "react-hook-form";
import { ChannelConfig } from "../../channels-configuration/channels-config";
import { countries } from "./countries";
import React from "react";
type CountrySelectProps = ControllerRenderProps<ChannelConfig, "address.country">;
// TODO: replace with macaw-ui component
const useStyles = makeStyles({
root: {
padding: "7px 9px !important",
},
clearIndicator: {
marginRight: 2,
},
});
// eslint-disable-next-line react/display-name
export const CountrySelect = React.forwardRef((p: CountrySelectProps, ref) => {
const styles = useStyles();
const { onChange, value } = p;
return (
<Autocomplete
classes={{
inputRoot: styles.root,
clearIndicator: styles.clearIndicator,
}}
options={countries}
onChange={(_, data) => onChange(data ? data.code : null)}
value={
countries.find((country) => {
return value === country.code;
}) ?? null
}
getOptionLabel={(option) => option.label}
renderInput={(params) => <TextField {...params} inputRef={ref} placeholder={"Country"} />}
/>
);
});