feat: interact with user settings/profile (#9)

This commit is contained in:
Divlo
2022-02-19 23:20:33 +01:00
committed by GitHub
parent 48debe8638
commit 7ac4f86cd5
101 changed files with 6705 additions and 9777 deletions

View File

@ -10,7 +10,7 @@ const errorObject: ErrorObject = {
}
describe('hooks/useForm/getErrorTranslationKey', () => {
it('returns `errors:invalid` with unknown keyword', async () => {
it('returns `errors:invalid` with unknown keyword', () => {
expect(
getErrorTranslationKey({
...errorObject,

View File

@ -0,0 +1,22 @@
import { Type } from '@sinclair/typebox'
import { handleCheckboxBoolean } from './handleCheckboxBoolean'
const schema = Type.Object({
myBoolean: Type.Boolean(),
myString: Type.String()
})
describe('hooks/useForm/handleCheckboxBoolean', () => {
it('should convert all checkbox property to boolean', () => {
const object = {
myBoolean: 'on',
myString: 'on'
}
const result = handleCheckboxBoolean(object, schema)
expect(result).toEqual({
myBoolean: true,
myString: 'on'
})
})
})

View File

@ -0,0 +1,25 @@
import type { TObject } from '@sinclair/typebox'
import type { ObjectAny } from '../../tools/types'
export const handleCheckboxBoolean = (
object: ObjectAny,
validateSchemaObject: TObject<ObjectAny>
): ObjectAny => {
const booleanProperties: string[] = []
for (const property in validateSchemaObject.properties) {
const rule = validateSchemaObject.properties[property]
if (rule.type === 'boolean') {
booleanProperties.push(property)
}
}
for (const booleanProperty of booleanProperties) {
if (object[booleanProperty] == null) {
object[booleanProperty] =
validateSchemaObject.properties[booleanProperty].default
} else {
object[booleanProperty] = object[booleanProperty] === 'on'
}
}
return object
}

View File

@ -0,0 +1,20 @@
import { replaceEmptyStringInObjectToNull } from './replaceEmptyStringInObjectToNull'
describe('tools/utils/replaceEmptyStringInObjectToNull', () => {
it('should replace empty string in object to null except for required properties', () => {
expect(
replaceEmptyStringInObjectToNull(
{
foo: '',
bar: 'bar',
baz: ''
},
['baz']
)
).toEqual({
foo: null,
bar: 'bar',
baz: ''
})
})
})

View File

@ -0,0 +1,19 @@
import type { ObjectAny } from '../../tools/types'
export const replaceEmptyStringInObjectToNull = (
object: ObjectAny,
required: string[] = []
): ObjectAny => {
return Object.fromEntries(
Object.entries(object).map(([key, value]) => {
if (
typeof value === 'string' &&
value.length === 0 &&
!required.includes(key)
) {
return [key, null]
}
return [key, value]
})
)
}

View File

@ -1,12 +1,15 @@
import { useMemo, useState } from 'react'
import useTranslation from 'next-translate/useTranslation'
import { Type } from '@sinclair/typebox'
import type { FormDataObject, HandleForm } from 'react-component-form'
import type { HandleForm } from 'react-component-form'
import type { ErrorObject } from 'ajv'
import { FetchState, useFetchState } from '../useFetchState'
import { ajv } from '../../tools/ajv'
import { getErrorTranslationKey } from './getErrorTranslationKey'
import { replaceEmptyStringInObjectToNull } from './replaceEmptyStringInObjectToNull'
import type { ObjectAny } from '../../tools/types'
import { handleCheckboxBoolean } from './handleCheckboxBoolean'
interface Errors {
[key: string]: ErrorObject<string, any> | null | undefined
@ -21,7 +24,9 @@ const findError = (
export type GetErrorTranslation = (error?: ErrorObject | null) => string | null
export interface UseFormOptions {
validateSchemaObject: { [key: string]: any }
validateSchema: { [key: string]: any }
replaceEmptyStringToNull?: boolean
resetOnSuccess?: boolean
}
export type HandleSubmit = (callback: HandleSubmitCallback) => HandleForm
@ -32,20 +37,28 @@ interface Message {
}
export type HandleSubmitCallback = (
formData: FormDataObject,
formData: ObjectAny,
formElement: HTMLFormElement
) => Promise<Message | null>
export interface UseFormResult {
message: string | null
setMessageTranslationKey: React.Dispatch<
React.SetStateAction<string | undefined>
>
fetchState: FetchState
setFetchState: React.Dispatch<React.SetStateAction<FetchState>>
getErrorTranslation: GetErrorTranslation
handleSubmit: HandleSubmit
errors: Errors
}
export const useForm = (options: UseFormOptions): UseFormResult => {
const { validateSchemaObject } = options
const {
validateSchema,
replaceEmptyStringToNull = false,
resetOnSuccess = false
} = options
const { t } = useTranslation()
const [fetchState, setFetchState] = useFetchState()
const [messageTranslationKey, setMessageTranslationKey] = useState<
@ -53,13 +66,13 @@ export const useForm = (options: UseFormOptions): UseFormResult => {
>(undefined)
const [errors, setErrors] = useState<Errors>({})
const validateSchema = useMemo(() => {
return Type.Object(validateSchemaObject)
}, [validateSchemaObject])
const validateSchemaObject = useMemo(() => {
return Type.Object(validateSchema)
}, [validateSchema])
const validate = useMemo(() => {
return ajv.compile(validateSchema)
}, [validateSchema])
return ajv.compile(validateSchemaObject)
}, [validateSchemaObject])
const getErrorTranslation = (error?: ErrorObject | null): string | null => {
if (error != null) {
@ -72,12 +85,19 @@ export const useForm = (options: UseFormOptions): UseFormResult => {
}
const handleSubmit: HandleSubmit = (callback) => {
return async (formData, formElement) => {
return async (formData: ObjectAny, formElement) => {
if (replaceEmptyStringToNull) {
formData = replaceEmptyStringInObjectToNull(
formData,
validateSchemaObject.required
)
}
formData = handleCheckboxBoolean(formData, validateSchemaObject)
const isValid = validate(formData)
if (!isValid) {
setFetchState('error')
const errors: Errors = {}
for (const property in validateSchema.properties) {
for (const property in validateSchemaObject.properties) {
errors[property] = validate.errors?.find(findError(`/${property}`))
}
setErrors(errors)
@ -89,7 +109,9 @@ export const useForm = (options: UseFormOptions): UseFormResult => {
setMessageTranslationKey(message.value)
if (message.type === 'success') {
setFetchState('success')
formElement.reset()
if (resetOnSuccess) {
formElement.reset()
}
} else {
setFetchState('error')
}
@ -102,7 +124,9 @@ export const useForm = (options: UseFormOptions): UseFormResult => {
getErrorTranslation,
errors,
fetchState,
setFetchState,
handleSubmit,
message: messageTranslationKey != null ? t(messageTranslationKey) : null
message: messageTranslationKey != null ? t(messageTranslationKey) : null,
setMessageTranslationKey
}
}