Skip to content

Commit

Permalink
v0.2
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker committed Dec 19, 2024
1 parent c78b161 commit af26e94
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 259 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as postalCode from './postalCode'
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import setData from '../../Form/data-context/setData'
import { GeneralConfig, fetchDataFromAPI } from '../'
import { UseFieldProps } from '../../types'

export type HandlerConfig = {
cityPath: string
}

async function fetchData(value: string, generalConfig: GeneralConfig) {
// Mock API response
// 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
generalConfig.fetchConfig.url = `https://cors-anywhere.herokuapp.com/https://api.bring.com/address/api/no/postal-codes/${value}`

return await fetchDataFromAPI(generalConfig)
}

export function onChange(
generalConfig: GeneralConfig,
handlerConfig?: HandlerConfig
): UseFieldProps<string>['onChange'] {
return async function onChange(value) {
if (typeof value === 'string' && value.length >= 4) {
try {
const data = await fetchData(value, generalConfig)
// console.log('onChange', generalConfig, handlerConfig, value, data)

const { city } = data.postal_codes[0] || {}
if (handlerConfig?.cityPath) {
const dataContext = setData(generalConfig.handlerId)
dataContext.update(handlerConfig.cityPath, city)
}
} catch (error) {
return new Error(error)
}
}
}
}

export function onBlurValidator(
generalConfig: GeneralConfig
): UseFieldProps<string>['onBlurValidator'] {
return async function onBlurValidator(value) {
try {
const data = await fetchData(value, generalConfig)
// console.log('onBlurValidator', generalConfig, value, data)

if (data.postal_codes?.[0]?.postal_code !== value) {
return new Error('💁‍♂️ Feil i postnummeret')
}
} catch (error) {
return new Error(error)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { SharedStateId } from '../../../shared/helpers/useSharedState'

export type GeneralConfig = {
handlerId?: SharedStateId
fetchConfig?: {
url?: string
headers?: Record<string, string>
}
}

export function createContext<GeneralConfigGeneric = GeneralConfig>(
generalConfig: GeneralConfigGeneric = null
) {
const handlerId = {}
if (!generalConfig['handlerId']) {
generalConfig['handlerId'] = handlerId
}

return {
handlerId,
withConfig<
HandlerMethod extends (
generalConfig: GeneralConfigGeneric,
handlerConfig: unknown
) => ReturnType<HandlerMethod>,
>(fn: HandlerMethod, handlerConfig?: Parameters<HandlerMethod>[1]) {
return fn(generalConfig, handlerConfig)
},
}
}

export async function fetchDataFromAPI(generalConfig: GeneralConfig) {
const { fetchConfig } = generalConfig

const options = {
method: 'GET',
headers: {
Accept: 'application/json',
...fetchConfig.headers,
},
}

try {
const response = await fetch(fetchConfig.url, options)

// Check if the response status is in the range of 200-299
if (!response.ok) {
throw new Error(
`HTTP Error: ${response.status} - ${response.statusText}`
)
}

return await response.json()
} catch (error) {
throw new Error(error)
}
}
2 changes: 2 additions & 0 deletions packages/dnb-eufemia/src/extensions/forms/Connectors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './createContext'
export * as Bring from './Bring'
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -23,7 +22,6 @@ export type Props = Pick<
*/
country?: Path | string
help?: HelpProps
connector?: Record<'postalCode' | 'city', ConnectorProps<string>>
}

function PostalCodeAndCity(props: Props) {
Expand All @@ -36,16 +34,9 @@ function PostalCodeAndCity(props: Props) {
help,
width = 'large',
country = defaultCountry,
connector,
...fieldBlockProps
} = props

const postalCodeConnector = useConnector<string>(
connector?.postalCode,
props
)
const cityConnector = useConnector<string>(connector?.city, props)

const {
pattern: cityPattern,
className: cityClassName,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -52,146 +51,55 @@ 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
*
*/

/**
* 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))
// },
},
}
// 1. Create a context with the config
const { withConfig, handlerId } = Connectors.createContext({
fetchConfig: {
headers: {
'X-Mybring-API-Uid': '',
'X-Mybring-API-Key': '',
},
},
}
})

// 2. Use the context to create the onBlurValidator and onChange functions
const onBlurValidator = withConfig(
Connectors.Bring.postalCode.onBlurValidator
)

// Should we name "onChange" to "autocompleteOnChange" or something like that?
const onChange = withConfig(Connectors.Bring.postalCode.onChange, {
cityPath: '/city', // or targetPath?
})

export function PostalCodeAPI_Draft() {
return (
<Form.Handler defaultData={{ postalCode: '123', city: 'Oslo' }}>
{/* <Field.PostalCodeAndCity
// import { External } from '@dnb/eufemia/extensions/forms'
// connector={External.BringConnector.postalCodeAndCity()}
postalCode={{
path: '/postalCode',
// onChange: async (value) => {
// 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' }}
/> */}

<Field.PostalCodeAndCity
postalCode={{
placeholder: '????',
onChange: (value) => console.log('postalCode onChange', value),
}}
city={{
placeholder: 'Your city',
onChange: (value) => console.log('city onChange', value),
}}
/>
<Form.Handler
id={handlerId}
// defaultData={{ postalCode: '000', city: 'Oslo' }}
onSubmit={async (data) => {
await new Promise((resolve) => setTimeout(resolve, 3000))
console.log('onSubmit', data)
}}
>
<Form.Card>
<Field.PostalCodeAndCity
postalCode={{
path: '/postalCode',
onChange,
onBlurValidator,
// validateInitially: true,
}}
city={{
path: '/city',
// onChange: async (value) => {
// await new Promise((resolve) => setTimeout(resolve, 2000))
// console.log('onChange', value)
// return { success: 'saved' }
// },
}}
/>
</Form.Card>
<Form.SubmitButton />
</Form.Handler>
)
}

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': '',
// // },
// // })
// // },
// }
Loading

0 comments on commit af26e94

Please sign in to comment.