From 173e3896b03753983b78da38b39c5ef8485dd234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Fri, 13 Dec 2024 21:44:09 +0100 Subject: [PATCH] v0.2 --- .../forms/Connectors/Bring/index.ts | 1 + .../forms/Connectors/Bring/postalCode.ts | 57 ++++++ .../src/extensions/forms/Connectors/index.ts | 15 ++ .../PostalCodeAndCity/PostalCodeAndCity.tsx | 15 +- .../stories/PostalCodeAndCity.stories.tsx | 177 ++++-------------- .../extensions/forms/hooks/useConnector.ts | 101 ---------- .../dnb-eufemia/src/extensions/forms/index.ts | 1 + .../dnb-eufemia/src/extensions/forms/types.ts | 7 - 8 files changed, 114 insertions(+), 260 deletions(-) create mode 100644 packages/dnb-eufemia/src/extensions/forms/Connectors/Bring/index.ts create mode 100644 packages/dnb-eufemia/src/extensions/forms/Connectors/Bring/postalCode.ts create mode 100644 packages/dnb-eufemia/src/extensions/forms/Connectors/index.ts delete mode 100644 packages/dnb-eufemia/src/extensions/forms/hooks/useConnector.ts diff --git a/packages/dnb-eufemia/src/extensions/forms/Connectors/Bring/index.ts b/packages/dnb-eufemia/src/extensions/forms/Connectors/Bring/index.ts new file mode 100644 index 00000000000..5e40c737aca --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Connectors/Bring/index.ts @@ -0,0 +1 @@ +export * as postalCode from './postalCode' diff --git a/packages/dnb-eufemia/src/extensions/forms/Connectors/Bring/postalCode.ts b/packages/dnb-eufemia/src/extensions/forms/Connectors/Bring/postalCode.ts new file mode 100644 index 00000000000..2a1a3278538 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Connectors/Bring/postalCode.ts @@ -0,0 +1,57 @@ +import setData from '../../Form/data-context/setData' + +export function onChange(apiConfig, connectionConfig) { + return async function onChange(value) { + const data = await verifyPostalCodeByAPI(value) + // console.log('onChange', apiConfig, connectionConfig, value, data) + + if (connectionConfig?.targetPath) { + const dataContext = setData(apiConfig.handlerId) + dataContext.update( + connectionConfig.targetPath, + data.postal_codes[0].city + ) + } + } +} + +export function onBlurValidator(apiConfig) { + return async function onBlurValidator(value) { + const data = await verifyPostalCodeByAPI(value) + // console.log('onBlurValidator', apiConfig, value, data) + + if (data.postal_codes[0].postal_code !== value) { + return new Error('💁‍♂️ Feil i postnummeret') + } + } +} + +// Mock API response +async function verifyPostalCodeByAPI(postalCode: string) { + await new Promise((resolve) => setTimeout(resolve, 800)) + + const mockData = { + city: 'Vollen', + county: 'Akershus', + latitude: '59.78899739297151', + longitude: '10.482494731266165', + municipality: 'Asker', + municipalityId: '3203', + po_box: false, + postal_code: '1391', + } + return { postal_codes: [mockData] } + + // Visit: https://cors-anywhere.herokuapp.com/corsdemo to enable this service + const url = `https://cors-anywhere.herokuapp.com/https://api.bring.com/address/api/no/postal-codes/${postalCode}` + const response = await fetch(url, { + method: 'GET', + headers: { + Accept: 'application/json', + 'X-Mybring-API-Uid': '', + 'X-Mybring-API-Key': '', + }, + }) + + return await response.json() +} diff --git a/packages/dnb-eufemia/src/extensions/forms/Connectors/index.ts b/packages/dnb-eufemia/src/extensions/forms/Connectors/index.ts new file mode 100644 index 00000000000..fb7403c0621 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Connectors/index.ts @@ -0,0 +1,15 @@ +export * as Bring from './Bring' + +export function createContext(config: T = null) { + const handlerId = {} + return { + handlerId, + withConfig( + fn: (config: T, connectionConfig: U) => U, + connectionConfig?: U + ) { + config['handlerId'] = handlerId + return fn(config, connectionConfig) + }, + } +} diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/PostalCodeAndCity/PostalCodeAndCity.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/PostalCodeAndCity/PostalCodeAndCity.tsx index 0c675b5e692..30acb32685e 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/PostalCodeAndCity/PostalCodeAndCity.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/PostalCodeAndCity/PostalCodeAndCity.tsx @@ -3,10 +3,9 @@ import classnames from 'classnames' import { Props as FieldBlockProps } from '../../FieldBlock' import StringField, { Props as StringFieldProps } from '../String' import CompositionField from '../Composition' -import { ConnectorProps, Path } from '../../types' +import { Path } from '../../types' import useTranslation from '../../hooks/useTranslation' import useDataValue from '../../hooks/useDataValue' -import useConnector from '../../hooks/useConnector' import { COUNTRY as defaultCountry } from '../../../../shared/defaults' import { HelpProps } from '../../../../components/help-button/HelpButtonInline' @@ -23,7 +22,6 @@ export type Props = Pick< */ country?: Path | string help?: HelpProps - connector?: Record<'postalCode' | 'city', ConnectorProps> } function PostalCodeAndCity(props: Props) { @@ -36,16 +34,9 @@ function PostalCodeAndCity(props: Props) { help, width = 'large', country = defaultCountry, - connector, ...fieldBlockProps } = props - const postalCodeConnector = useConnector( - connector?.postalCode, - props - ) - const cityConnector = useConnector(connector?.city, props) - const { pattern: cityPattern, className: cityClassName, @@ -107,8 +98,6 @@ function PostalCodeAndCity(props: Props) { mask={postalCodeMask} pattern={postalCodePattern} placeholder={postalCodePlaceHolder} - onChange={postalCodeConnector?.onChange} - onBlurValidator={postalCodeConnector?.onBlurValidator} errorMessages={useMemo( () => ({ 'Field.errorRequired': translations.PostalCode.errorRequired, @@ -135,8 +124,6 @@ function PostalCodeAndCity(props: Props) { cityClassName )} label={cityLabel ?? translations.City.label} - onChange={cityConnector?.onChange} - onBlurValidator={cityConnector?.onBlurValidator} errorMessages={useMemo( () => ({ 'Field.errorRequired': translations.City.errorRequired, diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/PostalCodeAndCity/stories/PostalCodeAndCity.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/PostalCodeAndCity/stories/PostalCodeAndCity.stories.tsx index d8445a89de0..42483879b06 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/PostalCodeAndCity/stories/PostalCodeAndCity.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/PostalCodeAndCity/stories/PostalCodeAndCity.stories.tsx @@ -1,5 +1,4 @@ -import { Field, Form, Value } from '../../..' -import { Flex } from '../../../../../components' +import { Connectors, Field, Form, Value } from '../../..' export default { title: 'Eufemia/Extensions/Forms/PostalCodeAndCity', @@ -52,146 +51,48 @@ export function PostalCodeAndCityCountryCodeSelection() { ) } -/** - * The idea is to add a new property called "connector" to every field that can be used with the Bring API. - * A connector allows a field to both support events like before, but also events from the connector. - * A connector is essentially a plugin that can be used to add support for APIs like Bring. - * Eufemia Forms delivers a default connector that devs can use with their own APIs and tokens etc. - * - * This is a draft on how we can deliver a flexible Bring API connection. - * It should be possible to customize / replace: - * - * - fetch routine - * - maybe it always should be defined? - * - how properties are mapped get and set - * - API url and headers - * - add a plugin system, so we can add support for other APIs than Bring - * - */ +const { withConfig, handlerId } = Connectors.createContext({ + foo: 'bar', +}) -/** - * This is a part of Eufemia: - */ -const External = { - BringConnector: { - postalCodeAndCity: (apiConnectionConfig = null) => { - return { - postalCode: { - // onChange: async (value) => { - // await new Promise((resolve) => setTimeout(resolve, 1000)) - // }, - onBlurValidator: async (value) => { - const data = await verifyPostalCodeByAPI(value) - if (data.postal_codes[0].postal_code !== value) { - return new Error('💁‍♂️ Feil i postnummeret') - } - }, - }, - city: { - // onChange: async (value) => { - // await new Promise((resolve) => setTimeout(resolve, 1000)) - // }, - }, - } - }, - }, -} +const onBlurValidator = withConfig( + Connectors.Bring.postalCode.onBlurValidator +) + +// Should we name "onChange" to "autocompleteOnChange" or something like that? +const onChange = withConfig(Connectors.Bring.postalCode.onChange, { + targetPath: '/city', +}) export function PostalCodeAPI_Draft() { return ( - - {/* { - // await new Promise((resolve) => setTimeout(resolve, 1000)) - // console.log('onChange', value) - // }, - onBlurValidator: async (value) => { - console.log('value', value) - await new Promise((resolve) => setTimeout(resolve, 2000)) - return new Error('💁‍♂️ Feil i postnummeret') - }, - validateInitially: true, - }} - city={{ path: '/city' }} - /> */} - - console.log('postalCode onChange', value), - }} - city={{ - placeholder: 'Your city', - onChange: (value) => console.log('city onChange', value), - }} - /> + { + await new Promise((resolve) => setTimeout(resolve, 3000)) + console.log('onSubmit', data) + }} + > + + { + // await new Promise((resolve) => setTimeout(resolve, 2000)) + // console.log('onChange', value) + // return { success: 'saved' } + // }, + }} + /> + + ) } - -async function verifyPostalCodeByAPI(postalCode: string) { - await new Promise((resolve) => setTimeout(resolve, 600)) - - const mockData = { - city: 'Vollen', - county: 'Akershus', - latitude: '59.78899739297151', - longitude: '10.482494731266165', - municipality: 'Asker', - municipalityId: '3203', - po_box: false, - postal_code: '1391', - } - return { postal_codes: [mockData] } - - // Visit: https://cors-anywhere.herokuapp.com/corsdemo to enable this service - const url = `https://cors-anywhere.herokuapp.com/https://api.bring.com/address/api/no/postal-codes/${postalCode}` - const response = await fetch(url, { - method: 'GET', - headers: { - Accept: 'application/json', - 'X-Mybring-API-Uid': '', - 'X-Mybring-API-Key': '', - }, - }) - - return await response.json() -} - -// // This is a config for a Bring Plugin -// const apiConnectionConfig = { -// // Optional -// connections: { -// // Optional -// postalCode: { -// // Optional -// url: ({ postalCode }) => -// `https://api.bring.com/address/api/no/postal-codes/${postalCode}`, -// // Optional -// // headers: { -// // 'X-Mybring-API-Uid': '', -// // }, -// }, -// }, -// // Optional -// sharedBetweenAllConnections: { -// headers: { -// 'X-Mybring-API-Uid': '', -// }, -// }, -// // Optional -// // fetch: async ({ url }) => { -// // return await fetch(url, { -// // method: 'GET', -// // headers: { -// // Accept: 'application/json', -// // 'X-Mybring-API-Uid': '', -// // 'X-Mybring-API-Key': '', -// // }, -// // }) -// // }, -// } diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/useConnector.ts b/packages/dnb-eufemia/src/extensions/forms/hooks/useConnector.ts deleted file mode 100644 index 5229448f42c..00000000000 --- a/packages/dnb-eufemia/src/extensions/forms/hooks/useConnector.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { isAsync } from '../../../shared/helpers/isAsync' -import { ConnectorProps } from '../types' - -import { useCallback } from 'react' - -export default function useConnector( - connector: ConnectorProps = {}, - props: Record & ConnectorProps = {} -): { - onChange: ConnectorProps['onChange'] - onBlurValidator: ConnectorProps['onBlurValidator'] -} { - const { handler: onBlurValidator, asyncHandler: onBlurValidatorAsync } = - useCombinedHandlers(connector.onBlurValidator, props.onBlurValidator) - const { handler: onChange, asyncHandler: onChangeAsync } = - useCombinedHandlers(connector.onChange, props.onChange) - - const onChangeRefs = [connector.onChange, props.onChange] - const onBlurValidatorRefs = [ - connector.onBlurValidator, - props.onBlurValidator, - ] - - return { - onChange: isFunctionInArray(onChangeRefs) - ? isAsyncInArray(onChangeRefs) - ? onChangeAsync - : onChange - : undefined, - onBlurValidator: isFunctionInArray(onBlurValidatorRefs) - ? isAsyncInArray(onBlurValidatorRefs) - ? onBlurValidatorAsync - : onBlurValidator - : undefined, - } -} - -function useCombinedHandlers( - connectorFn?: (...args: unknown[]) => unknown, - propsFn?: (...args: unknown[]) => unknown -) { - const handler = useCallback( - (...args: unknown[]) => { - { - const result = propsFn?.apply(this, args) - if (result) { - return result - } - } - - { - const result = connectorFn?.apply(this, args) - if (result) { - return result - } - } - }, - [connectorFn, propsFn] - ) - - const asyncHandler = useCallback( - async (...args: unknown[]) => { - { - const result = await propsFn?.apply(this, args) - if (result) { - return result - } - } - - { - const result = await connectorFn?.apply(this, args) - if (result) { - return result - } - } - }, - [connectorFn, propsFn] - ) - - return { - handler: - [connectorFn, propsFn].filter(Boolean).length > 0 - ? handler - : undefined, - asyncHandler: - [connectorFn, propsFn].filter(Boolean).length > 0 - ? asyncHandler - : undefined, - } -} - -function isAsyncInArray(array: Array) { - return array.some((fn) => { - return isAsync(fn) - }) -} -function isFunctionInArray(array: Array) { - return array.some((fn) => { - return typeof fn === 'function' - }) -} diff --git a/packages/dnb-eufemia/src/extensions/forms/index.ts b/packages/dnb-eufemia/src/extensions/forms/index.ts index 2101e067d7c..ae208ee0d55 100644 --- a/packages/dnb-eufemia/src/extensions/forms/index.ts +++ b/packages/dnb-eufemia/src/extensions/forms/index.ts @@ -8,6 +8,7 @@ export * as Wizard from './Wizard' export * as DataContext from './DataContext' export * as Iterate from './Iterate' export * as Tools from './Tools' +export * as Connectors from './Connectors' export { default as FieldBlock } from './FieldBlock' export { default as ValueBlock } from './ValueBlock' export { default as Ajv } from 'ajv/dist/2020' diff --git a/packages/dnb-eufemia/src/extensions/forms/types.ts b/packages/dnb-eufemia/src/extensions/forms/types.ts index 34702fdc12e..89bbcf96a57 100644 --- a/packages/dnb-eufemia/src/extensions/forms/types.ts +++ b/packages/dnb-eufemia/src/extensions/forms/types.ts @@ -265,11 +265,6 @@ export type DataValueReadWriteComponentProps< DataValueReadProps & DataValueWriteProps -export type ConnectorProps = Pick< - UseFieldProps, - 'onChange' | 'onBlurValidator' -> - export interface UseFieldProps< Value = unknown, EmptyValue = undefined | unknown, @@ -405,8 +400,6 @@ export interface UseFieldProps< * For internal use only. */ valueType?: string | number | boolean | Array - - connector?: ConnectorProps } export type FieldProps<