From e641882badeb6311430eed228407cac9ee7d28c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Boixader=20G=C3=BCell?= Date: Sat, 30 Mar 2024 21:26:43 +0100 Subject: [PATCH] wip: strict --- src/guillo-gmi/components/TdLink.tsx | 2 +- .../components/behaviors/iworkflow.tsx | 4 +- src/guillo-gmi/components/input/dropdown.tsx | 2 +- .../components/input/search_input.tsx | 62 +++++++---------- .../components/input/search_input_list.tsx | 68 ++++++++----------- .../components/input/select_vocabulary.tsx | 2 +- src/guillo-gmi/components/login.tsx | 4 +- .../components/panel/permissions.tsx | 8 +-- .../components/panel/permissions_prinperm.tsx | 6 +- .../components/panel/permissions_prinrole.tsx | 6 +- .../components/panel/permissions_roleperm.tsx | 6 +- .../components/panel/properties.tsx | 4 +- .../components/search_vocabulary_labels.tsx | 2 +- src/guillo-gmi/forms/required_fields.tsx | 8 +-- src/guillo-gmi/hooks/useClickAway.tsx | 26 +++++-- src/guillo-gmi/hooks/useCrudContext.tsx | 12 ++-- src/guillo-gmi/hooks/useLocation.tsx | 41 ++++++----- src/guillo-gmi/hooks/useSetState.tsx | 48 ++++++++----- src/guillo-gmi/hooks/useVocabulary.tsx | 4 +- src/guillo-gmi/lib/auth.ts | 22 +++--- src/guillo-gmi/lib/client.tsx | 51 ++++++++++++-- src/guillo-gmi/lib/utils.ts | 14 ++++ src/guillo-gmi/models/sharing.ts | 14 ++-- src/guillo-gmi/types/guillotina.ts | 8 --- 24 files changed, 244 insertions(+), 180 deletions(-) diff --git a/src/guillo-gmi/components/TdLink.tsx b/src/guillo-gmi/components/TdLink.tsx index a64336ba..ddf041e4 100644 --- a/src/guillo-gmi/components/TdLink.tsx +++ b/src/guillo-gmi/components/TdLink.tsx @@ -9,7 +9,7 @@ interface Props { style?: IndexSignature } export function TdLink({ model, children, style = {} }: Props) { - const link = useRef() + const link = useRef(null) function onClick() { if (link && link.current) { diff --git a/src/guillo-gmi/components/behaviors/iworkflow.tsx b/src/guillo-gmi/components/behaviors/iworkflow.tsx index 72eb318c..7009dc26 100644 --- a/src/guillo-gmi/components/behaviors/iworkflow.tsx +++ b/src/guillo-gmi/components/behaviors/iworkflow.tsx @@ -77,8 +77,8 @@ export function IWorkflow() { } const getStateTitle = () => { - if (vocabulary.data?.items?.length > 0) { - const vocabularyValue = vocabulary.data.items.find( + if ((vocabulary.data?.items ?? []).length > 0) { + const vocabularyValue = vocabulary?.data?.items.find( (item) => item.token === currentState ) if (vocabularyValue) { diff --git a/src/guillo-gmi/components/input/dropdown.tsx b/src/guillo-gmi/components/input/dropdown.tsx index 5cd672d5..3da80289 100644 --- a/src/guillo-gmi/components/input/dropdown.tsx +++ b/src/guillo-gmi/components/input/dropdown.tsx @@ -20,7 +20,7 @@ export default function Dropdown({ optionDisabledWhen, options, }: Props) { - const ref = useRef(null) + const ref = useRef(null) const [isActive, setIsActive] = useState(false) const position = isRight ? 'is-right' : '' const status = isActive diff --git a/src/guillo-gmi/components/input/search_input.tsx b/src/guillo-gmi/components/input/search_input.tsx index 92832ccb..0589479a 100644 --- a/src/guillo-gmi/components/input/search_input.tsx +++ b/src/guillo-gmi/components/input/search_input.tsx @@ -9,25 +9,10 @@ import { useConfig } from '../../hooks/useConfig' import { useIntl } from 'react-intl' import { genericMessages } from '../../locales/generic_messages' import useClickAway from '../../hooks/useClickAway' -import { get } from '../../lib/utils' +import { debounce, get } from '../../lib/utils' import { SearchItem } from '../../types/guillotina' import { Traversal } from '../../contexts' - -function debounce(func, wait) { - let timeout - return function () { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const context = this - // eslint-disable-next-line prefer-rest-params - const args = arguments - const later = function () { - timeout = null - func.apply(context, args) - } - clearTimeout(timeout) - timeout = setTimeout(later, wait) - } -} +import { IndexSignature } from '../../types/global' interface State { page: number @@ -37,7 +22,7 @@ interface State { } const initialState: State = { page: 0, - items: undefined, + items: [], loading: false, items_total: 0, } @@ -46,7 +31,7 @@ interface Props { onChange?: (value: string) => void error?: string errorZoneClassName?: string - traversal?: Traversal + traversal: Traversal path?: string qs?: string[][] queryCondition?: string @@ -64,8 +49,8 @@ export const SearchInput = ({ onChange, error, errorZoneClassName, - traversal = null, - path = null, + traversal, + path = undefined, qs = [], queryCondition = 'id__in', value, @@ -73,8 +58,8 @@ export const SearchInput = ({ dataTestWrapper = 'wrapperSearchInputTest', dataTestSearchInput = 'searchInputTest', dataTestItem = 'searchInputItemTest', - renderTextItemOption = null, - typeNameQuery = null, + renderTextItemOption = undefined, + typeNameQuery = undefined, labelProperty = 'id', }: Props) => { const intl = useIntl() @@ -82,9 +67,11 @@ export const SearchInput = ({ const [isOpen, setIsOpen] = useState(false) const [searchTerm, setSearchTerm] = useState('') const inputRef = useRef(null) - const wrapperRef = useRef(null) + const wrapperRef = useRef(null) const { PageSize, SearchEngine } = useConfig() - const [valueLabel, setValueLabel] = useState>(undefined) + const [valueLabel, setValueLabel] = useState | undefined>( + undefined + ) const [uid] = useState(generateUID('search_input')) useClickAway(wrapperRef, () => { @@ -115,13 +102,13 @@ export const SearchInput = ({ const searchTermParsed = [`id`, value] const { get: getSearch } = traversal.registry const fnName = getSearch('searchEngineQueryParamsFunction', SearchEngine) - const qsParsed = traversal.client[fnName]({ + const qsParsed = traversal.client.getQueryParamsSearchFunction(fnName)({ path: traversal.path, start: 0, pageSize: PageSize, withDepth: false, }) - let typeNameParsed = [] + let typeNameParsed: string[][] = [] if (typeNameQuery) { typeNameParsed = parser(`type_name__in=${typeNameQuery}`) } @@ -138,16 +125,19 @@ export const SearchInput = ({ ...typeNameParsed, ]) } - const data = await traversal.client.search( + const data = await traversal.client.search( path ? path : traversal.client.getContainerFromPath(traversal.path), searchTermQs, false, false ) - const newValuesLabel = data.items.reduce((result, item) => { - result[item.id] = get(item, labelProperty, item.id) - return result - }, {}) + const newValuesLabel = data.items.reduce>( + (result, item) => { + result[item.id] = get(item, labelProperty, item.id) + return result + }, + {} + ) setValueLabel(newValuesLabel) } } @@ -155,7 +145,7 @@ export const SearchInput = ({ const handleSearch = async (page = 0, concat = false, value = '') => { setOptions({ loading: true }) let searchTermQs = '' - let searchTermParsed = [] + let searchTermParsed: string[][] = [] if (value !== '') { searchTermParsed = parser(`${queryCondition}=${value}`) } @@ -168,7 +158,7 @@ export const SearchInput = ({ withDepth: false, }) const sortParsed = parser(`_sort_des=${labelProperty}`) - let typeNameParsed = [] + let typeNameParsed: string[][] = [] if (typeNameQuery) { typeNameParsed = parser(`type_name__in=${typeNameQuery}`) } @@ -188,7 +178,7 @@ export const SearchInput = ({ ]) } - const data = await traversal.client.search( + const data = await traversal.client.search( path ? path : traversal.client.getContainerFromPath(traversal.path), searchTermQs, false, @@ -205,7 +195,7 @@ export const SearchInput = ({ }) } - const renderTextItemOptionFn = (item) => { + const renderTextItemOptionFn = (item: SearchItem) => { if (renderTextItemOption) { return renderTextItemOption(item) } diff --git a/src/guillo-gmi/components/input/search_input_list.tsx b/src/guillo-gmi/components/input/search_input_list.tsx index a47a19a0..e1dda135 100644 --- a/src/guillo-gmi/components/input/search_input_list.tsx +++ b/src/guillo-gmi/components/input/search_input_list.tsx @@ -10,25 +10,10 @@ import { useConfig } from '../../hooks/useConfig' import { useIntl } from 'react-intl' import { genericMessages } from '../../locales/generic_messages' import useClickAway from '../../hooks/useClickAway' -import { get } from '../../lib/utils' +import { debounce, get } from '../../lib/utils' import { SearchItem } from '../../types/guillotina' import { Traversal } from '../../contexts' - -function debounce(func, wait) { - let timeout - return function () { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const context = this - // eslint-disable-next-line prefer-rest-params - const args = arguments - const later = function () { - timeout = null - func.apply(context, args) - } - clearTimeout(timeout) - timeout = setTimeout(later, wait) - } -} +import { IndexSignature } from '../../types/global' interface State { page: number @@ -38,7 +23,7 @@ interface State { } const initialState: State = { page: 0, - items: undefined, + items: [], loading: false, items_total: 0, } @@ -47,9 +32,9 @@ interface Props { onChange: (value: string[]) => void error?: string errorZoneClassName?: string - traversal?: Traversal + traversal: Traversal path?: string - qs?: string[] + qs?: string[][] queryCondition?: string value: string[] btnClass?: string @@ -65,8 +50,8 @@ export const SearchInputList = ({ onChange, error, errorZoneClassName, - traversal = null, - path = null, + traversal, + path = undefined, qs = [], queryCondition = 'id__in', value, @@ -74,17 +59,19 @@ export const SearchInputList = ({ dataTestWrapper = 'wrapperSearchInputTest', dataTestSearchInput = 'searchInputTest', dataTestItem = 'searchInputItemTest', - renderTextItemOption = null, - typeNameQuery = null, + renderTextItemOption = undefined, + typeNameQuery = undefined, labelProperty = 'id', }: Props) => { const intl = useIntl() const [options, setOptions] = useSetState(initialState) - const [valuesLabel, setValuesLabels] = useState(undefined) + const [valuesLabel, setValuesLabels] = useState( + undefined + ) const [isOpen, setIsOpen] = useState(false) const [searchTerm, setSearchTerm] = useState('') const inputRef = useRef(null) - const wrapperRef = useRef(null) + const wrapperRef = useRef(null) const { PageSize, SearchEngine } = useConfig() const [isLoadingData, setIsLoadingData] = useState(false) @@ -115,7 +102,7 @@ export const SearchInputList = ({ const handleSearch = async (page = 0, concat = false, value = '') => { setOptions({ loading: true }) let searchTermQs = '' - let searchTermParsed = [] + let searchTermParsed: string[][] = [] if (value !== '') { searchTermParsed = parser(`${queryCondition}=${value}`) } @@ -128,7 +115,7 @@ export const SearchInputList = ({ withDepth: false, }) const sortParsed = parser(`_sort_des=${labelProperty}`) - let typeNameParsed = [] + let typeNameParsed: string[][] = [] if (typeNameQuery) { typeNameParsed = parser(`type_name__in=${typeNameQuery}`) } @@ -148,13 +135,13 @@ export const SearchInputList = ({ ]) } - const data = await traversal.client.search( + const data = await traversal.client.search( path ? path : traversal.client.getContainerFromPath(traversal.path), searchTermQs, false, false ) - const newItems = + const newItems: SearchItem[] = options.items && concat ? [...options.items, ...data.items] : data.items setOptions({ @@ -172,13 +159,13 @@ export const SearchInputList = ({ const searchTermParsed = ['__or', `id=${value.join('%26id=')}`] const { get: getSearch } = traversal.registry const fnName = getSearch('searchEngineQueryParamsFunction', SearchEngine) - const qsParsed = traversal.client[fnName]({ + const qsParsed = traversal.client.getQueryParamsSearchFunction(fnName)({ path: traversal.path, start: 0, pageSize: 100, withDepth: false, }) - let typeNameParsed = [] + let typeNameParsed: string[][] = [] if (typeNameQuery) { typeNameParsed = parser(`type_name__in=${typeNameQuery}`) } @@ -195,18 +182,19 @@ export const SearchInputList = ({ ...typeNameParsed, ]) } - const data = await traversal.client.search( + const data = await traversal.client.search( path ? path : traversal.client.getContainerFromPath(traversal.path), searchTermQs, false, - false, - 0, - 100 + false + ) + const newValuesLabel = data.items.reduce>( + (result, item) => { + result[item.id] = get(item, labelProperty, item.id) + return result + }, + {} ) - const newValuesLabel = data.items.reduce((result, item) => { - result[item.id] = get(item, labelProperty, item.id) - return result - }, {}) setValuesLabels(newValuesLabel) setIsLoadingData(false) } diff --git a/src/guillo-gmi/components/input/select_vocabulary.tsx b/src/guillo-gmi/components/input/select_vocabulary.tsx index 19d49214..04dc46d0 100644 --- a/src/guillo-gmi/components/input/select_vocabulary.tsx +++ b/src/guillo-gmi/components/input/select_vocabulary.tsx @@ -37,7 +37,7 @@ export const SelectVocabulary = forwardRef( if ( get(vocabulary, 'data.items', null) ) { - const vocData = vocabulary.data.items.map((item) => { + const vocData = (vocabulary?.data?.items ?? []).map((item) => { return { text: item.title, value: item.token, diff --git a/src/guillo-gmi/components/login.tsx b/src/guillo-gmi/components/login.tsx index 0d9a08f8..8637c7c5 100644 --- a/src/guillo-gmi/components/login.tsx +++ b/src/guillo-gmi/components/login.tsx @@ -10,12 +10,12 @@ interface State { username: string password: string loading: boolean - errors: string + errors?: string } const initialState = { username: '', password: '', - loading: undefined, + loading: false, errors: undefined, } diff --git a/src/guillo-gmi/components/panel/permissions.tsx b/src/guillo-gmi/components/panel/permissions.tsx index 8ce3b572..29ea20df 100644 --- a/src/guillo-gmi/components/panel/permissions.tsx +++ b/src/guillo-gmi/components/panel/permissions.tsx @@ -250,12 +250,12 @@ export function AddPermission({ refresh, reset }: AddPermissionProps) { const principalsData = await Ctx.client.getPrincipals(Ctx.path) const groups = principalsData.groups.map((group) => ({ - text: group.id, - value: group.id, + text: group['@name'], + value: group['@name'], })) const users = principalsData.users.map((user) => ({ - text: user.fullname || user.id, - value: user.id, + text: user.fullname || user['@name'], + value: user['@name'], })) principals = [...groups, ...users] diff --git a/src/guillo-gmi/components/panel/permissions_prinperm.tsx b/src/guillo-gmi/components/panel/permissions_prinperm.tsx index 0a430c57..a79b2143 100644 --- a/src/guillo-gmi/components/panel/permissions_prinperm.tsx +++ b/src/guillo-gmi/components/panel/permissions_prinperm.tsx @@ -8,10 +8,10 @@ import { useIntl } from 'react-intl' import { genericMessages } from '../../locales/generic_messages' interface State { - principal: string + principal?: string permission: string[] - setting: string - error: string + setting?: string + error?: string } interface Props { principals?: { text: string; value: string }[] diff --git a/src/guillo-gmi/components/panel/permissions_prinrole.tsx b/src/guillo-gmi/components/panel/permissions_prinrole.tsx index 8c049cb6..583019d4 100644 --- a/src/guillo-gmi/components/panel/permissions_prinrole.tsx +++ b/src/guillo-gmi/components/panel/permissions_prinrole.tsx @@ -8,10 +8,10 @@ import { useIntl } from 'react-intl' import { genericMessages } from '../../locales/generic_messages' interface State { - principal: string + principal?: string roles: string[] - setting: string - error: string + setting?: string + error?: string } interface Props { principals?: { text: string; value: string }[] diff --git a/src/guillo-gmi/components/panel/permissions_roleperm.tsx b/src/guillo-gmi/components/panel/permissions_roleperm.tsx index 47849e61..fddb7d75 100644 --- a/src/guillo-gmi/components/panel/permissions_roleperm.tsx +++ b/src/guillo-gmi/components/panel/permissions_roleperm.tsx @@ -8,10 +8,10 @@ import { useIntl } from 'react-intl' import { genericMessages } from '../../locales/generic_messages' interface State { - role: string + role?: string permission: string[] - setting: string - error: string + setting?: string + error?: string } interface Props { roles: { text: string; value: string }[] diff --git a/src/guillo-gmi/components/panel/properties.tsx b/src/guillo-gmi/components/panel/properties.tsx index 28b8df5a..1758c2f5 100644 --- a/src/guillo-gmi/components/panel/properties.tsx +++ b/src/guillo-gmi/components/panel/properties.tsx @@ -34,7 +34,7 @@ const _ignoreFields = [ ] interface State { - data: GuillotinaSchema + data?: GuillotinaSchema loading: boolean error: string | unknown } @@ -67,7 +67,7 @@ export function PanelProperties() { .filter((key) => !ignoreFields.includes(key)) .map((key) => ({ key, - value: schema.data.properties[key] as GuillotinaSchemaProperty, + value: schema?.data?.properties[key] as GuillotinaSchemaProperty, })) useEffect(() => { diff --git a/src/guillo-gmi/components/search_vocabulary_labels.tsx b/src/guillo-gmi/components/search_vocabulary_labels.tsx index 2fb57187..edad3ae6 100644 --- a/src/guillo-gmi/components/search_vocabulary_labels.tsx +++ b/src/guillo-gmi/components/search_vocabulary_labels.tsx @@ -18,7 +18,7 @@ export function SearchVocabularyLabels({ query = 'q', vocabularyName }: Props) { let value: string = defaultRenderValue if ((vocabulary?.data?.items ?? []).length > 0) { - const vocabularyValue = vocabulary.data.items.find( + const vocabularyValue = vocabulary?.data?.items.find( (item) => item.token === value ) if (vocabularyValue) { diff --git a/src/guillo-gmi/forms/required_fields.tsx b/src/guillo-gmi/forms/required_fields.tsx index b2b74ecd..d6fac406 100644 --- a/src/guillo-gmi/forms/required_fields.tsx +++ b/src/guillo-gmi/forms/required_fields.tsx @@ -11,7 +11,7 @@ const ignoreFiels: string[] = [] const extraFields = ['title'] interface Props { - onSubmit: (data: { [key: string]: any }) => void + onSubmit: (data: IndexSignature) => void actionName?: string title?: string dataTest?: string @@ -19,9 +19,9 @@ interface Props { type: string } interface State { - data: any + data: IndexSignature loading: boolean - error: any + error?: unknown formFields: string[] } export function RequiredFieldsForm({ @@ -41,7 +41,7 @@ export function RequiredFieldsForm({ const [errors, setErrors] = useState>({}) const [schema, setSchema] = useSetState({ - data: undefined, + data: {}, loading: false, error: undefined, formFields: [], diff --git a/src/guillo-gmi/hooks/useClickAway.tsx b/src/guillo-gmi/hooks/useClickAway.tsx index e7807699..e9562e22 100644 --- a/src/guillo-gmi/hooks/useClickAway.tsx +++ b/src/guillo-gmi/hooks/useClickAway.tsx @@ -1,10 +1,22 @@ import { useEffect, useRef } from 'react' const defaultEvents = ['mousedown', 'touchstart'] -const on = (obj, ...args) => obj.addEventListener(...args) -const off = (obj, ...args) => obj.removeEventListener(...args) +const on = ( + obj: Document, + type: keyof DocumentEventMap, + handler: (ev: Event) => void +) => obj.addEventListener(type, handler) +const off = ( + obj: Document, + type: keyof DocumentEventMap, + handler: (ev: Event) => void +) => obj.removeEventListener(type, handler) -export default function useClickAway(ref, onClickAway, events = defaultEvents) { +export default function useClickAway( + ref: React.RefObject, + onClickAway: (event: Event) => void, + events = defaultEvents +) { const savedCallback = useRef(onClickAway) useEffect(() => { @@ -12,18 +24,18 @@ export default function useClickAway(ref, onClickAway, events = defaultEvents) { }, [onClickAway]) useEffect(() => { - const handler = (event) => { + const handler = (event: Event) => { const { current: el } = ref - el && !el.contains(event.target) && savedCallback.current(event) + el && !el.contains(event.target as Node) && savedCallback.current(event) } for (const eventName of events) { - on(document, eventName, handler) + on(document, eventName as keyof DocumentEventMap, handler) } return () => { for (const eventName of events) { - off(document, eventName, handler) + off(document, eventName as keyof DocumentEventMap, handler) } } }, [events, ref]) diff --git a/src/guillo-gmi/hooks/useCrudContext.tsx b/src/guillo-gmi/hooks/useCrudContext.tsx index a390ee86..790b00c7 100644 --- a/src/guillo-gmi/hooks/useCrudContext.tsx +++ b/src/guillo-gmi/hooks/useCrudContext.tsx @@ -2,14 +2,14 @@ import { Traversal, useTraversal } from '../contexts' import { processResponse } from '../lib/processResponse' import useSetState from './useSetState' -interface State { +interface State { loading?: boolean isError?: boolean errorMessage?: string result?: T response?: unknown } -const initial: State = { +const initial = { loading: undefined, isError: false, errorMessage: undefined, @@ -42,7 +42,11 @@ function patch( } function del(setState: (value: Partial>) => void, Ctx: Traversal) { - return async (data = {}, endpoint?: string, body = false): Promise => { + return async ( + data = {}, + endpoint?: string, + body = false + ): Promise> => { setState({ loading: true }) let newState = {} try { @@ -80,7 +84,7 @@ function post(setState: (value: Partial>) => void, Ctx: Traversal) { } function get(setState: (value: Partial>) => void, Ctx: Traversal) { - return async (endpoint?: string): Promise => { + return async (endpoint?: string): Promise> => { setState({ loading: true }) let newState = {} try { diff --git a/src/guillo-gmi/hooks/useLocation.tsx b/src/guillo-gmi/hooks/useLocation.tsx index d8cfb147..ace36faf 100644 --- a/src/guillo-gmi/hooks/useLocation.tsx +++ b/src/guillo-gmi/hooks/useLocation.tsx @@ -4,7 +4,7 @@ import { IndexSignature } from '../types/global' // Mostly inspired from // https://github.com/molefrog/wouter -const setURLParams = (p) => { +const setURLParams = (p: URLSearchParams) => { return window.history.pushState( 0, '0', @@ -12,7 +12,7 @@ const setURLParams = (p) => { ) } -const clean = (to) => { +const clean = (to: IndexSignature) => { const current = new URLSearchParams() Object.keys(to).forEach((_key) => current.set(_key, to[_key])) setURLParams(current) @@ -57,7 +57,7 @@ export const useLocation = (): [ // the function reference should stay the same between re-renders, so that // it can be passed down as an element prop without any performance concerns. const navigate = useCallback( - (to, replace) => { + (to: IndexSignature, replace?: boolean) => { if (replace) { clean(to) return @@ -70,7 +70,7 @@ export const useLocation = (): [ ) const remove = useCallback( - (param) => { + (param: string) => { const current = new URLSearchParams(path.toString()) current.delete(param) setURLParams(current) @@ -91,19 +91,28 @@ let patched = 0 const patchHistoryEvents = () => { if (patched) return - ;['pushState', 'replaceState'].map((type) => { - const original = window.history[type] - window.history[type] = function (...args) { - const result = original.apply(this, args) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const event: any = new Event(type) - event.arguments = args - - dispatchEvent(event) - return result - } - }) + const originalPushState = window.history.pushState + window.history.pushState = function (...args) { + const result = originalPushState.apply(this, args) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const event: any = new Event('pushState') + event.arguments = args + + dispatchEvent(event) + return result + } + + const originalReplaceState = window.history.replaceState + window.history.replaceState = function (...args) { + const result = originalReplaceState.apply(this, args) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const event: any = new Event('replaceState') + event.arguments = args + + dispatchEvent(event) + return result + } return (patched = 1) } diff --git a/src/guillo-gmi/hooks/useSetState.tsx b/src/guillo-gmi/hooks/useSetState.tsx index c86ed902..fa537b8d 100644 --- a/src/guillo-gmi/hooks/useSetState.tsx +++ b/src/guillo-gmi/hooks/useSetState.tsx @@ -1,21 +1,33 @@ -import { useCallback, useState } from 'react' +import { useState, useCallback } from 'react' -export default function useSetState( - initialState -): [T, (value: Partial) => void] { - const [state, set] = useState(initialState) - const setState = useCallback( - (patch) => { - set((prevState) => - Object.assign( - {}, - prevState, - patch instanceof Function ? patch(prevState) : patch - ) - ) - }, - [set] - ) +// **** Types **** // + +export type TSetState = (newPartialState: Partial) => void + +// **** Functions **** // - return [state, setState] +/** + * Do setState like react class component. + */ +function useSetState(initialState: T): [T, TSetState] { + const [state, setState] = useState(initialState) + // Function which accepts a partial state to merge + const setCustomState = useCallback((newPartialState: Partial) => { + try { + setState( + (prevState): T => { + return { ...prevState, ...newPartialState } + } + ) + } catch (error) { + // eslint-disable-next-line no-console + console.error(error) + } + }, []) + // Return + return [state, setCustomState] } + +// **** Export Default **** // + +export default useSetState diff --git a/src/guillo-gmi/hooks/useVocabulary.tsx b/src/guillo-gmi/hooks/useVocabulary.tsx index c3762957..d8c0a3ca 100644 --- a/src/guillo-gmi/hooks/useVocabulary.tsx +++ b/src/guillo-gmi/hooks/useVocabulary.tsx @@ -4,11 +4,11 @@ import useSetState from './useSetState' import { useEffect } from 'react' interface State { - data: GuillotinaVocabulary + data?: GuillotinaVocabulary loading: boolean error: any } -export function useVocabulary(vocabularyName: string, path: string = null) { +export function useVocabulary(vocabularyName: string, path?: string) { const traversal = useTraversal() const [vocabulary, setVocabulary] = useSetState({ diff --git a/src/guillo-gmi/lib/auth.ts b/src/guillo-gmi/lib/auth.ts index 23688b6c..fe2066d2 100644 --- a/src/guillo-gmi/lib/auth.ts +++ b/src/guillo-gmi/lib/auth.ts @@ -7,23 +7,23 @@ export class Auth { events = {} url: string base_url: string - errors: string + errors?: string - constructor(url) { + constructor(url: string) { this.url = url this.base_url = url this.errors = undefined } - getUrl(endpoint) { + getUrl(endpoint: string) { return `${this.url}${endpoint}` } - setAccount(account) { + setAccount(account: string) { this.url = this.base_url + account } - async login(username, password) { + async login(username: string, password: string) { const url = this.getUrl('@login') try { const data = await fetch(url, { @@ -68,12 +68,12 @@ export class Auth { return false } const [token] = this._getToken() - const data: IndexSignature = jwt_decode(token) + const data: IndexSignature = jwt_decode(token as string) console.log(token) return data.id } - storeAuth(data) { + storeAuth(data: { token: string; exp: number }) { localStorage.setItem('auth', data.token) localStorage.setItem( 'auth_expires', @@ -111,7 +111,7 @@ export class Auth { return res.token } - willExpire(expiration) { + willExpire(expiration: string) { const now = new Date().getTime() if (parseInt(expiration) * 1000 < now + 10 * 1000) { return true @@ -119,7 +119,7 @@ export class Auth { return false } - isExpired(expiration) { + isExpired(expiration: string) { const now = new Date().getTime() if (parseInt(expiration) * 1000 > now) { return false @@ -136,11 +136,11 @@ export class Auth { return token } - getHeaders() { + getHeaders(): HeadersInit { const [authToken, expires] = this._getToken() if (!authToken) return {} - if (this.willExpire(expires) && this.retryRefresh < this.maxRetry) { + if (this.willExpire(expires ?? '') && this.retryRefresh < this.maxRetry) { // eslint-disable-next-line no-extra-semi ;(async () => await this.refreshToken())() } diff --git a/src/guillo-gmi/lib/client.tsx b/src/guillo-gmi/lib/client.tsx index 58732c2d..a54848e5 100644 --- a/src/guillo-gmi/lib/client.tsx +++ b/src/guillo-gmi/lib/client.tsx @@ -8,7 +8,7 @@ import { IndexSignature, LightFile } from '../types/global' import { ItemModel } from '../models' import { Auth } from './auth' import { - GuillotinaGroups, + GuillotinaGroup, GuillotinaUser, ReturnSearchCompatible, } from '../types/guillotina' @@ -314,16 +314,55 @@ export class GuillotinaClient { async getPrincipals( path: string ): Promise<{ - groups: GuillotinaGroups[] + groups: GuillotinaGroup[] users: GuillotinaUser[] }> { const groups = this.getGroups(path) const users = this.getUsers(path) - const [gr, usr] = await Promise.all([groups, users]) - + const [responseGroups, responseUsers] = await Promise.all([groups, users]) + let groupsData: GuillotinaGroup[] = [] + let usersData: GuillotinaUser[] = [] + + if (responseGroups.ok) { + const groupsDataResponse = await responseGroups.json() + groupsData = groupsDataResponse.map( + (group: { + '@name': string + id: string + title: string + users: string[] + roles: string[] + }) => { + return { + '@name': group.id, + user_roles: group.roles, + users: group.users, + } + } + ) + } + if (responseUsers) { + const usersDataResponse = await responseUsers.json() + usersData = usersDataResponse.map( + (user: { + '@name': string + id: string + fullname: string + email: string + roles: string[] + }) => { + return { + '@name': user.id, + user_roles: user.roles, + fullname: user.fullname, + email: user.email, + } + } + ) + } return { - groups: gr.ok ? await gr.json() : [], - users: usr.ok ? await usr.json() : [], + groups: groupsData, + users: usersData, } } diff --git a/src/guillo-gmi/lib/utils.ts b/src/guillo-gmi/lib/utils.ts index ab5ce37a..0d9405d1 100644 --- a/src/guillo-gmi/lib/utils.ts +++ b/src/guillo-gmi/lib/utils.ts @@ -35,3 +35,17 @@ export function getNewId(id = '') { return `${suffix}${num + 1}` }) } + +export function debounce( + callback: (...args: T) => PromiseLike | U, + wait: number +) { + let timer: ReturnType + + return (...args: T): Promise => { + clearTimeout(timer) + return new Promise((resolve) => { + timer = setTimeout(() => resolve(callback(...args)), wait) + }) + } +} diff --git a/src/guillo-gmi/models/sharing.ts b/src/guillo-gmi/models/sharing.ts index 4a4929af..1711ea4d 100644 --- a/src/guillo-gmi/models/sharing.ts +++ b/src/guillo-gmi/models/sharing.ts @@ -17,14 +17,18 @@ export class Sharing { local: GuillotinaSharingMap inherit: GuillotinaSharingInheritItem[] - constructor(element) { - Object.assign(this, element || base) + constructor(element: GuillotinaSharing | undefined) { + if (element === undefined) { + throw new Error('Sharing element is undefined') + } + this.local = element.local || base.local + this.inherit = element.inherit || base.inherit } get roles() { return Object.keys(this.local.roleperm) } - getRole(role) { + getRole(role: string) { return this.local.roleperm[role] } @@ -32,7 +36,7 @@ export class Sharing { return Object.keys(this.local.prinperm) } - getPrincipals(principal) { + getPrincipals(principal: string) { return this.local.prinperm[principal] } @@ -40,7 +44,7 @@ export class Sharing { return Object.keys(this.local.prinrole) } - getPrinroles(role) { + getPrinroles(role: string) { return this.local.prinrole[role] } } diff --git a/src/guillo-gmi/types/guillotina.ts b/src/guillo-gmi/types/guillotina.ts index 0c4f1d5b..2d538f81 100644 --- a/src/guillo-gmi/types/guillotina.ts +++ b/src/guillo-gmi/types/guillotina.ts @@ -188,14 +188,6 @@ export enum Setting { Unset = 'Unset', } -export interface GuillotinaGroups { - '@name': string - id: string - title: null - users: string[] - roles: string[] -} - export interface Workflow { '@id': string history: History[]