From 84e9ca5d66a809261355867b7d2d344a9efdbfa9 Mon Sep 17 00:00:00 2001 From: Adrian Pilarczyk Date: Fri, 21 Apr 2023 07:55:43 +0200 Subject: [PATCH] feat: add country select (#410) * feat: :sparkles: add country-select * feat: :sparkles: add taxjar address-factory * feat: :sparkles: add validateAddress methods to [provider]-client * build: :construction_worker: add changeset --- .changeset/healthy-mayflies-hang.md | 5 + .../taxes/src/modules/avatax/avatax-client.ts | 11 + .../modules/avatax/maps/address-factory.ts | 4 +- .../channels/ui/channel-tax-provider-form.tsx | 10 +- .../taxjar/maps/address-factory.test.ts | 68 ++++ .../modules/taxjar/maps/address-factory.ts | 32 ++ .../taxes/src/modules/taxjar/taxjar-client.ts | 14 +- .../modules/ui/country-select/countries.ts | 382 ++++++++++++++++++ .../ui/country-select/country-select.tsx | 43 ++ 9 files changed, 557 insertions(+), 12 deletions(-) create mode 100644 .changeset/healthy-mayflies-hang.md create mode 100644 apps/taxes/src/modules/taxjar/maps/address-factory.test.ts create mode 100644 apps/taxes/src/modules/taxjar/maps/address-factory.ts create mode 100644 apps/taxes/src/modules/ui/country-select/countries.ts create mode 100644 apps/taxes/src/modules/ui/country-select/country-select.tsx diff --git a/.changeset/healthy-mayflies-hang.md b/.changeset/healthy-mayflies-hang.md new file mode 100644 index 0000000..b779c40 --- /dev/null +++ b/.changeset/healthy-mayflies-hang.md @@ -0,0 +1,5 @@ +--- +"saleor-app-taxes": minor +--- + +Add `` and use it in the `channel-tax-provider-form.ts` to ensure the correct formatting of the country code. diff --git a/apps/taxes/src/modules/avatax/avatax-client.ts b/apps/taxes/src/modules/avatax/avatax-client.ts index d0ea3ff..21a7bd4 100644 --- a/apps/taxes/src/modules/avatax/avatax-client.ts +++ b/apps/taxes/src/modules/avatax/avatax-client.ts @@ -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); + } } diff --git a/apps/taxes/src/modules/avatax/maps/address-factory.ts b/apps/taxes/src/modules/avatax/maps/address-factory.ts index d1275e0..2a55cd5 100644 --- a/apps/taxes/src/modules/avatax/maps/address-factory.ts +++ b/apps/taxes/src/modules/avatax/maps/address-factory.ts @@ -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 { diff --git a/apps/taxes/src/modules/channels/ui/channel-tax-provider-form.tsx b/apps/taxes/src/modules/channels/ui/channel-tax-provider-form.tsx index 2e00915..8910945 100644 --- a/apps/taxes/src/modules/channels/ui/channel-tax-provider-form.tsx +++ b/apps/taxes/src/modules/channels/ui/channel-tax-provider-form.tsx @@ -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({ @@ -158,14 +158,10 @@ export const ChannelTaxProviderForm = () => { - {/* // todo: add country select */} ( - - )} + render={({ field }) => } /> diff --git a/apps/taxes/src/modules/taxjar/maps/address-factory.test.ts b/apps/taxes/src/modules/taxjar/maps/address-factory.test.ts new file mode 100644 index 0000000..4f02500 --- /dev/null +++ b/apps/taxes/src/modules/taxjar/maps/address-factory.test.ts @@ -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", + }); + }); + }); +}); diff --git a/apps/taxes/src/modules/taxjar/maps/address-factory.ts b/apps/taxes/src/modules/taxjar/maps/address-factory.ts new file mode 100644 index 0000000..bdf3f59 --- /dev/null +++ b/apps/taxes/src/modules/taxjar/maps/address-factory.ts @@ -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, +}; diff --git a/apps/taxes/src/modules/taxjar/taxjar-client.ts b/apps/taxes/src/modules/taxjar/taxjar-client.ts index d4d3812..619679f 100644 --- a/apps/taxes/src/modules/taxjar/taxjar-client.ts +++ b/apps/taxes/src/modules/taxjar/taxjar-client.ts @@ -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); + } } diff --git a/apps/taxes/src/modules/ui/country-select/countries.ts b/apps/taxes/src/modules/ui/country-select/countries.ts new file mode 100644 index 0000000..cae7a53 --- /dev/null +++ b/apps/taxes/src/modules/ui/country-select/countries.ts @@ -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" }, +]; diff --git a/apps/taxes/src/modules/ui/country-select/country-select.tsx b/apps/taxes/src/modules/ui/country-select/country-select.tsx new file mode 100644 index 0000000..3c821a1 --- /dev/null +++ b/apps/taxes/src/modules/ui/country-select/country-select.tsx @@ -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; + +// 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 ( + onChange(data ? data.code : null)} + value={ + countries.find((country) => { + return value === country.code; + }) ?? null + } + getOptionLabel={(option) => option.label} + renderInput={(params) => } + /> + ); +});