-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Forms): add Bring API connector for postal code validation and a…
…utofill city (#4554) - **Preview:** [Bring connector](https://eufemia-git-feat-forms-api-connectors-bring-eufemia.vercel.app/uilib/extensions/forms/Connectors/Bring/) - **Fully Treeshakeable:** Ensures no unused code is bundled. - **Flexible and Extendable:** Designed to adapt to future needs. - **Leverages Existing APIs:** Built on current field props and APIs. ### Example code: ```tsx import { Form, Connectors } from '@dnb/eufemia/extensions/forms' // 1. Create a context with the config const { withConfig, handlerId } = Connectors.createContext({ fetchConfig: { url: '...', headers: { 'X-Mybring-API-Uid': '', }, }, }) // 2. Use the context to create the onChangeValidator and onChange functions const onChangeValidator = withConfig(Connectors.Bring.postalCode.validator) // Should we name ".postalCode.onChange" to ".postalCode.autocompleteOnChange" or something like that? const onChange = withConfig(Connectors.Bring.postalCode.autofill, { cityPath: '/city', }) render( <Form.Handler id={handlerId} > <Field.PostalCodeAndCity postalCode={{ path: '/postalCode', onChange, onChangeValidator, }} city={{ path: '/city', }} /> </Form.Handler> ) ``` --------- Co-authored-by: Anders <[email protected]>
- Loading branch information
1 parent
8d0d980
commit f2ccd5d
Showing
22 changed files
with
1,734 additions
and
6 deletions.
There are no files selected for viewing
19 changes: 19 additions & 0 deletions
19
packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Connectors.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
--- | ||
title: 'Connectors' | ||
description: 'Connectors are an opt-in way to extend the functionality of a form. They can be used to add features like API calls for autofill, validation, and more.' | ||
showTabs: true | ||
order: 21 | ||
tabs: | ||
- title: Info | ||
key: '/info' | ||
breadcrumb: | ||
- text: Forms | ||
href: /uilib/extensions/forms/ | ||
- text: Connectors | ||
href: /uilib/extensions/forms/Connectors/ | ||
accordion: true | ||
--- | ||
|
||
import Info from 'Docs/uilib/extensions/forms/Connectors/info' | ||
|
||
<Info /> |
25 changes: 25 additions & 0 deletions
25
...s/dnb-design-system-portal/src/docs/uilib/extensions/forms/Connectors/Bring.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
--- | ||
title: 'Bring' | ||
description: 'Bring is a connector that allows you to fetch data from their REST API and use it in your form.' | ||
showTabs: true | ||
hideInMenu: true | ||
tabs: | ||
- title: Info | ||
key: '/info' | ||
- title: Demos | ||
key: '/demos' | ||
breadcrumb: | ||
- text: Forms | ||
href: /uilib/extensions/forms/ | ||
- text: Connectors | ||
href: /uilib/extensions/forms/Connectors/ | ||
- text: Bring | ||
href: /uilib/extensions/forms/Connectors/Bring/ | ||
accordion: true | ||
--- | ||
|
||
import Info from 'Docs/uilib/extensions/forms/Connectors/Bring/info' | ||
import Demos from 'Docs/uilib/extensions/forms/Connectors/Bring/demos' | ||
|
||
<Info /> | ||
<Demos /> |
75 changes: 75 additions & 0 deletions
75
...es/dnb-design-system-portal/src/docs/uilib/extensions/forms/Connectors/Bring/Examples.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import ComponentBox from '../../../../../../shared/tags/ComponentBox' | ||
import { getMockData } from '@dnb/eufemia/src/extensions/forms/Connectors/Bring/postalCode' | ||
import { Form, Field, Connectors } from '@dnb/eufemia/src/extensions/forms' | ||
|
||
let mockFetchTimeout = null | ||
async function mockFetch(countryCode: string) { | ||
const originalFetch = globalThis.fetch | ||
|
||
globalThis.fetch = () => { | ||
return Promise.resolve({ | ||
ok: true, | ||
json: () => { | ||
return Promise.resolve(getMockData(countryCode)) | ||
}, | ||
}) as any | ||
} | ||
|
||
await new Promise((resolve) => setTimeout(resolve, 1000)) | ||
|
||
clearTimeout(mockFetchTimeout) | ||
mockFetchTimeout = setTimeout(() => { | ||
globalThis.fetch = originalFetch | ||
}, 1100) | ||
} | ||
|
||
export const PostalCode = () => { | ||
return ( | ||
<ComponentBox scope={{ Connectors, getMockData, mockFetch }}> | ||
{() => { | ||
const { withConfig } = Connectors.createContext({ | ||
fetchConfig: { | ||
url: async (value, { countryCode }) => { | ||
await mockFetch(countryCode) | ||
return '[YOUR-API-URL]/' + value | ||
}, | ||
}, | ||
}) | ||
|
||
const onBlurValidator = withConfig( | ||
Connectors.Bring.postalCode.validator, | ||
) | ||
|
||
const onChange = withConfig(Connectors.Bring.postalCode.autofill, { | ||
cityPath: '/city', | ||
}) | ||
|
||
return ( | ||
<Form.Handler onSubmit={console.log}> | ||
<Form.Card> | ||
<Field.SelectCountry | ||
path="/countryCode" | ||
defaultValue="NO" | ||
filterCountries={({ iso }) => ['NO', 'SE'].includes(iso)} | ||
/> | ||
<Field.PostalCodeAndCity | ||
countryCode="/countryCode" | ||
postalCode={{ | ||
path: '/postalCode', | ||
onBlurValidator, | ||
onChange, | ||
required: true, | ||
}} | ||
city={{ | ||
path: '/city', | ||
required: true, | ||
}} | ||
/> | ||
</Form.Card> | ||
<Form.SubmitButton /> | ||
</Form.Handler> | ||
) | ||
}} | ||
</ComponentBox> | ||
) | ||
} |
12 changes: 12 additions & 0 deletions
12
...design-system-portal/src/docs/uilib/extensions/forms/Connectors/Bring/demos.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
--- | ||
showTabs: true | ||
hideInMenu: true | ||
--- | ||
|
||
import * as Examples from './Examples' | ||
|
||
## Demos | ||
|
||
This demo contains only a mocked API call, so only a postal code of `1391` for Norway and `11432` for Sweden is valid. | ||
|
||
<Examples.PostalCode /> |
106 changes: 106 additions & 0 deletions
106
...-design-system-portal/src/docs/uilib/extensions/forms/Connectors/Bring/info.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
--- | ||
showTabs: true | ||
--- | ||
|
||
import { supportedCountryCodes } from '@dnb/eufemia/src/extensions/forms/Connectors/Bring/postalCode' | ||
|
||
## Description | ||
|
||
The `Bring` connector allows you to use the [Bring API](https://developer.bring.com/api/) to: | ||
|
||
- Verify a postal code | ||
- Autofill a city name or street name | ||
|
||
## PostalCode API | ||
|
||
Here is an example of how to use the Bring [Postal Code API](https://developer.bring.com/api/postal-code/) to connect the [PostalCodeAndCity](/uilib/extensions/forms/feature-fields/PostalCodeAndCity/) field to a form. | ||
|
||
First, create a context with the config: | ||
|
||
```tsx | ||
import { Connectors, Field, Form } from '@dnb/eufemia/extensions/forms' | ||
|
||
const { withConfig } = Connectors.createContext({ | ||
fetchConfig: { | ||
url: (value, { countryCode }) => { | ||
return `[YOUR-API-URL]/.../${countryCode}/.../${value}` | ||
// Real-world example using Bring's Postal Code API's get postal code endpoint, directly without proxy: | ||
// return `https://api.bring.com/address/api/{countryCode}/postal-codes/{value}` | ||
}, | ||
}, | ||
}) | ||
``` | ||
|
||
`[YOUR-API-URL]` is the URL of your own API endpoint that proxies the Bring [Postal Code API](https://developer.bring.com/api/postal-code/) with a token. | ||
|
||
### Supported countries | ||
|
||
The Bring API for PostalCode supports the [following countries](https://developer.bring.com/api/postal-code/#supported-countries), by its country codes: | ||
|
||
{supportedCountryCodes.join(', ')} | ||
|
||
### Endpoints and response format | ||
|
||
Ensure you use one of the [following endpoints](https://developer.bring.com/api/postal-code/#endpoints) from Bring via your proxy API, returning a list of postal codes in the following format: | ||
|
||
```json | ||
{ | ||
"postal_codes": [ | ||
{ | ||
"postal_code": "1391", | ||
"city": "Vollen" | ||
... | ||
} | ||
] | ||
} | ||
``` | ||
|
||
### To verify a postal code | ||
|
||
Use the context to create a validator based on the `validator` connector. | ||
|
||
You can use it for an `onChangeValidator` or `onBlurValidator` (recommended), depending on your use case. | ||
|
||
```tsx | ||
const onBlurValidator = withConfig(Connectors.Bring.postalCode.validator) | ||
|
||
function MyForm() { | ||
return ( | ||
<Form.Handler> | ||
<Field.PostalCodeAndCity | ||
postalCode={{ | ||
path: '/postalCode', | ||
onBlurValidator, | ||
}} | ||
/> | ||
</Form.Handler> | ||
) | ||
} | ||
``` | ||
|
||
### To autofill a city name based on a postal code | ||
|
||
Use the context to create the `onChange` event handler based on the `autofill` connector. | ||
|
||
```tsx | ||
const onChange = withConfig(Connectors.Bring.postalCode.autofill, { | ||
cityPath: '/city', | ||
}) | ||
|
||
function MyForm() { | ||
return ( | ||
<Form.Handler> | ||
<Field.PostalCodeAndCity | ||
postalCode={{ | ||
path: '/postalCode', | ||
onChange, | ||
}} | ||
city={{ | ||
path: '/city', | ||
}} | ||
/> | ||
<Form.SubmitButton /> | ||
</Form.Handler> | ||
) | ||
} | ||
``` |
100 changes: 100 additions & 0 deletions
100
...es/dnb-design-system-portal/src/docs/uilib/extensions/forms/Connectors/info.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
--- | ||
showTabs: true | ||
--- | ||
|
||
## Description | ||
|
||
`Connectors` are an opt-in way to extend the functionality of a form. They can be used to add features like API calls for autofill, validation, and more. | ||
|
||
Available connectors: | ||
|
||
- [Bring](/uilib/extensions/forms/Connectors/Bring/) | ||
|
||
## Import | ||
|
||
```ts | ||
import { Connectors } from '@dnb/eufemia/extensions/forms' | ||
``` | ||
|
||
## How to create your own connector | ||
|
||
Connectors are created by returning a function that takes the `generalConfig` and optionally a `handlerConfig` as an argument. | ||
|
||
Here is an example of how to create a connector that can be used as an field `onChangeValidator` or `onBlurValidator`: | ||
|
||
```ts | ||
export function validator(generalConfig: GeneralConfig) { | ||
// - The handler to be used as the validator | ||
return async function validatorHandler(value) { | ||
try { | ||
const { data, status } = await fetchData(value, { | ||
generalConfig, | ||
parameters: {}, | ||
}) | ||
|
||
const onMatch = () => { | ||
return new FormError('PostalCodeAndCity.invalidCode') | ||
} | ||
|
||
const { matcher } = responseResolver(data, handlerConfig) | ||
const match = matcher(value) | ||
|
||
if (status !== 400 && !match) { | ||
return onMatch() | ||
} | ||
} catch (error) { | ||
return error | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Here is the `GeneralConfig` type simplified: | ||
|
||
```ts | ||
type GeneralConfig = { | ||
fetchConfig?: { | ||
url: string | ((value: string) => string | Promise<string>) | ||
headers?: HeadersInit | ||
} | ||
} | ||
``` | ||
The `responseResolver` is used to take care of the response from the API and return the `matcher` and `payload` to be used by the connector. | ||
```ts | ||
const responseResolver: ResponseResolver< | ||
PostalCodeResolverData, | ||
PostalCodeResolverPayload | ||
> = (data, handlerConfig) => { | ||
// - Here we align the data from the API with the expected data structure | ||
const { postal_code, city } = data?.postal_codes?.[0] || {} | ||
|
||
return { | ||
/** | ||
* The matcher to be used to determine if the connector, | ||
* such as an validator for `onChangeValidator` or `onBlurValidator`, | ||
* should validate the field value. | ||
*/ | ||
matcher: (value) => value === postal_code, | ||
|
||
/** | ||
* The payload to be returned and used by the connector. | ||
*/ | ||
payload: { city }, | ||
} | ||
} | ||
``` | ||
|
||
You can extend a response resolver to support a custom resolver, given via the `handlerConfig` argument. | ||
|
||
```ts | ||
const responseResolver = (data, handlerConfig) => { | ||
const resolver = handlerConfig?.responseResolver | ||
if (typeof resolver === 'function') { | ||
return resolver(data) | ||
} | ||
|
||
// ... the rest of the response resolver. | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.