From de28aea086500b0b97eebf1f75662f8c7d34fdcc Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 31 May 2026 21:58:11 +0200 Subject: [PATCH] refactor(typescript): add proper types to Preferences container files Remove @ts-nocheck directives and add TypeScript interfaces for Preferences container components (Users, Roles, Accountant, Branches, Currencies). Replace duplicated withUserPreferences HOC with existing withDialogActions. Install @types/flat for the flat module. Co-Authored-By: Claude Opus 4.7 --- packages/webapp/package.json | 1 + .../Accountant/Accountant.schema.tsx | 1 - .../Preferences/Accountant/Accountant.tsx | 3 +- .../Preferences/Accountant/AccountantForm.tsx | 1 - .../Accountant/AccountantFormPage.tsx | 30 +++++++--------- .../Accountant/AccountantFormProvider.tsx | 34 ++++++++++--------- .../Branches/BranchesDataTable.tsx | 5 +-- .../Preferences/Branches/BranchesProvider.tsx | 33 +++++++++++------- .../Preferences/Currencies/Currencies.tsx | 1 - .../Currencies/CurrenciesDataTable.tsx | 2 +- .../Roles/RolesLanding/RolesDataTable.tsx | 27 ++++----------- .../Users/Roles/RolesLanding/RolesList.tsx | 2 -- .../Roles/RolesLanding/RolesListProvider.tsx | 29 ++++++++++------ .../Users/Roles/RolesLanding/components.tsx | 17 ++++++---- .../containers/Preferences/Users/Users.tsx | 15 +++----- .../Preferences/Users/UsersActions.tsx | 8 ++--- .../Preferences/Users/UsersDataTable.tsx | 2 +- .../Preferences/Users/UsersList.tsx | 17 +++------- .../Preferences/Users/UsersProvider.tsx | 29 ++++++++++------ .../Preferences/Users/withUserPreferences.tsx | 17 ---------- pnpm-lock.yaml | 8 +++++ 21 files changed, 131 insertions(+), 151 deletions(-) delete mode 100644 packages/webapp/src/containers/Preferences/Users/withUserPreferences.tsx diff --git a/packages/webapp/package.json b/packages/webapp/package.json index 6c4d08666..c3569fdec 100644 --- a/packages/webapp/package.json +++ b/packages/webapp/package.json @@ -129,6 +129,7 @@ "yup": "^0.28.1" }, "devDependencies": { + "@types/flat": "^5.0.5", "@vitejs/plugin-legacy": "^5.4.2", "@vitejs/plugin-react": "^4.3.4", "eslint-config-react-app": "^7.0.1", diff --git a/packages/webapp/src/containers/Preferences/Accountant/Accountant.schema.tsx b/packages/webapp/src/containers/Preferences/Accountant/Accountant.schema.tsx index b52b2404e..f8ed0f260 100644 --- a/packages/webapp/src/containers/Preferences/Accountant/Accountant.schema.tsx +++ b/packages/webapp/src/containers/Preferences/Accountant/Accountant.schema.tsx @@ -1,4 +1,3 @@ -// @ts-nocheck import * as Yup from 'yup'; const Schema = Yup.object().shape({ diff --git a/packages/webapp/src/containers/Preferences/Accountant/Accountant.tsx b/packages/webapp/src/containers/Preferences/Accountant/Accountant.tsx index 7df91e377..8a1261cbd 100644 --- a/packages/webapp/src/containers/Preferences/Accountant/Accountant.tsx +++ b/packages/webapp/src/containers/Preferences/Accountant/Accountant.tsx @@ -1,6 +1,5 @@ -// @ts-nocheck import React from 'react'; -import AccountantFormPage from './AccountantFormPage'; +import { AccountantFormPage } from './AccountantFormPage'; import { AccountantFormProvider } from './AccountantFormProvider'; /** diff --git a/packages/webapp/src/containers/Preferences/Accountant/AccountantForm.tsx b/packages/webapp/src/containers/Preferences/Accountant/AccountantForm.tsx index 457f3ae5a..deb320919 100644 --- a/packages/webapp/src/containers/Preferences/Accountant/AccountantForm.tsx +++ b/packages/webapp/src/containers/Preferences/Accountant/AccountantForm.tsx @@ -5,7 +5,6 @@ import { Form, useFormikContext } from 'formik'; import styled from 'styled-components'; import { FormGroup, Radio, Button, Intent } from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; - import { FormattedMessage as T, AccountsSelect, diff --git a/packages/webapp/src/containers/Preferences/Accountant/AccountantFormPage.tsx b/packages/webapp/src/containers/Preferences/Accountant/AccountantFormPage.tsx index 00fc995ad..da3e713f6 100644 --- a/packages/webapp/src/containers/Preferences/Accountant/AccountantFormPage.tsx +++ b/packages/webapp/src/containers/Preferences/Accountant/AccountantFormPage.tsx @@ -1,16 +1,13 @@ -// @ts-nocheck -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import * as R from 'ramda'; import intl from 'react-intl-universal'; -import { Formik } from 'formik'; +import { Formik, FormikHelpers } from 'formik'; import { Intent } from '@blueprintjs/core'; import { flatten, unflatten } from 'flat'; - import { AppToaster } from '@/components'; -import { withDashboardActions } from '@/containers/Dashboard/withDashboardActions'; -import { withSettings } from '@/containers/Settings/withSettings'; - -import AccountantForm from './AccountantForm'; +import { withDashboardActions, type WithDashboardActionsProps } from '@/containers/Dashboard/withDashboardActions'; +import { withSettings, type WithSettingsProps } from '@/containers/Settings/withSettings'; +import { AccountantForm } from './AccountantForm'; import { AccountantSchema } from './Accountant.schema'; import { useAccountantFormContext } from './AccountantFormProvider'; import { transferObjectOptionsToArray } from './utils'; @@ -35,14 +32,12 @@ const defaultFormValues = flatten({ }, }); -// Accountant preferences. -function AccountantFormPage({ - //# withDashboardActions - changePreferencesPageTitle, +interface AccountantFormPageInnerProps extends WithDashboardActionsProps, WithSettingsProps {} - // #withSettings +function AccountantFormPageInner({ + changePreferencesPageTitle, allSettings, -}) { +}: AccountantFormPageInnerProps) { const { saveSettingMutate } = useAccountantFormContext(); useEffect(() => { @@ -53,8 +48,7 @@ function AccountantFormPage({ ...defaultFormValues, ...transformToForm(flatten(allSettings), defaultFormValues), }); - // Handle the form submitting. - const handleFormSubmit = (values, { setSubmitting }) => { + const handleFormSubmit = (values: Record, { setSubmitting }: FormikHelpers>) => { const options = R.compose( transferObjectOptionsToArray, transfromToSnakeCase, @@ -84,9 +78,9 @@ function AccountantFormPage({ ); } -export default compose( +export const AccountantFormPage = compose( withSettings(({ allSettings }) => ({ allSettings, })), withDashboardActions, -)(AccountantFormPage); +)(AccountantFormPageInner); diff --git a/packages/webapp/src/containers/Preferences/Accountant/AccountantFormProvider.tsx b/packages/webapp/src/containers/Preferences/Accountant/AccountantFormProvider.tsx index c9a08f117..cebe797e7 100644 --- a/packages/webapp/src/containers/Preferences/Accountant/AccountantFormProvider.tsx +++ b/packages/webapp/src/containers/Preferences/Accountant/AccountantFormProvider.tsx @@ -1,35 +1,35 @@ -// @ts-nocheck -import React from 'react'; +import React, { ReactNode } from 'react'; import classNames from 'classnames'; import styled from 'styled-components'; +import type { AccountsList } from '@bigcapital/sdk-ts'; import { Card } from '@/components'; import { CLASSES } from '@/constants/classes'; import { useAccounts, useSaveSettings, useSettings } from '@/hooks/query'; import PreferencesPageLoader from '../PreferencesPageLoader'; -const AccountantFormContext = React.createContext(); +interface AccountantFormContextValue { + accounts: AccountsList | undefined; + isAccountsLoading: boolean; + saveSettingMutate: ReturnType['mutateAsync']; +} -/** - * Accountant data provider. - */ -function AccountantFormProvider({ ...props }) { - // Fetches the accounts list. +const AccountantFormContext = React.createContext({} as AccountantFormContextValue); + +interface AccountantFormProviderProps { + children: ReactNode; +} + +function AccountantFormProvider({ children, ...props }: AccountantFormProviderProps) { const { isLoading: isAccountsLoading, data: accounts } = useAccounts(); - - // Fetches Organization Settings. const { isLoading: isSettingsLoading } = useSettings(); - - // Save Organization Settings. const { mutateAsync: saveSettingMutate } = useSaveSettings(); - // Provider state. - const provider = { + const provider: AccountantFormContextValue = { accounts, isAccountsLoading, saveSettingMutate, }; - // Detarmines whether if any query is loading. const isLoading = isSettingsLoading || isAccountsLoading; return ( @@ -43,7 +43,9 @@ function AccountantFormProvider({ ...props }) { {isLoading ? ( ) : ( - + + {children} + )} diff --git a/packages/webapp/src/containers/Preferences/Branches/BranchesDataTable.tsx b/packages/webapp/src/containers/Preferences/Branches/BranchesDataTable.tsx index 10adbde4e..ff35057e9 100644 --- a/packages/webapp/src/containers/Preferences/Branches/BranchesDataTable.tsx +++ b/packages/webapp/src/containers/Preferences/Branches/BranchesDataTable.tsx @@ -26,10 +26,7 @@ function BranchesDataTable({ // #withAlertActions openAlert, }) { - // Table columns. const columns = useBranchesTableColumns(); - - // MarkBranchAsPrimary const { mutateAsync: markBranchAsPrimaryMutate } = useMarkBranchAsPrimary(); const { branches, isBranchesLoading, isBranchesFetching } = @@ -59,7 +56,7 @@ function BranchesDataTable({ ( + {} as BranchesContextValue, +); + +interface BranchesProviderProps { + query?: Record; + children: ReactNode; +} + +function BranchesProvider({ query, ...props }: BranchesProviderProps) { // Features guard. const { featureCan } = useFeatureCan(); - const isBranchFeatureCan = featureCan(Features.Branches); // Fetches the branches list. const { isLoading: isBranchesLoading, - isFetching: isBranchesFetching, data: branches, } = useBranches(query, { enabled: isBranchFeatureCan }); @@ -30,10 +38,9 @@ function BranchesProvider({ query, ...props }) { (isEmpty(branches) && !isBranchesLoading) || !isBranchFeatureCan; // Provider state. - const provider = { + const provider: BranchesContextValue = { branches, isBranchesLoading, - isBranchesFetching, isEmptyStatus, }; @@ -49,5 +56,7 @@ function BranchesProvider({ query, ...props }) { ); } -const useBranchesContext = () => React.useContext(BranchesContext); +const useBranchesContext = () => + React.useContext(BranchesContext); + export { BranchesProvider, useBranchesContext }; diff --git a/packages/webapp/src/containers/Preferences/Currencies/Currencies.tsx b/packages/webapp/src/containers/Preferences/Currencies/Currencies.tsx index 0cb88c967..9310c928f 100644 --- a/packages/webapp/src/containers/Preferences/Currencies/Currencies.tsx +++ b/packages/webapp/src/containers/Preferences/Currencies/Currencies.tsx @@ -2,7 +2,6 @@ import React from 'react'; import classNames from 'classnames'; import styled from 'styled-components'; - import { Card } from '@/components'; import { CLASSES } from '@/constants/classes'; import CurrenciesList from './CurrenciesList'; diff --git a/packages/webapp/src/containers/Preferences/Currencies/CurrenciesDataTable.tsx b/packages/webapp/src/containers/Preferences/Currencies/CurrenciesDataTable.tsx index 919ab4d58..b6e730933 100644 --- a/packages/webapp/src/containers/Preferences/Currencies/CurrenciesDataTable.tsx +++ b/packages/webapp/src/containers/Preferences/Currencies/CurrenciesDataTable.tsx @@ -49,7 +49,7 @@ function CurrenciesDataTable({ return ( { + const handleDeleteRole = ({ id, predefined }: { id: number; predefined: boolean }) => { if (predefined) { AppToaster.show({ message: intl.get('roles.error.you_cannot_delete_predefined_roles'), @@ -40,8 +28,7 @@ function RolesDataTable({ openAlert('role-delete', { roleId: id }); } }; - // Handles the edit of the given role. - const handleEditRole = ({ id, predefined }) => { + const handleEditRole = ({ id, predefined }: { id: number; predefined: boolean }) => { if (predefined) { AppToaster.show({ message: intl.get('roles.error.you_cannot_edit_predefined_roles'), @@ -55,7 +42,7 @@ function RolesDataTable({ return ( ({} as RolesListContextValue); + +interface RolesListProviderProps { + children: ReactNode; +} + +function RolesListProvider({ children, ...props }: RolesListProviderProps) { const { data: roles, isFetching: isRolesFetching, isLoading: isRolesLoading, } = useRoles(); - // Provider state. - const provider = { + const provider: RolesListContextValue = { roles, isRolesFetching, isRolesLoading, @@ -30,7 +35,9 @@ function RolesListProvider({ ...props }) { CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_USERS, )} > - + + {children} + ); } diff --git a/packages/webapp/src/containers/Preferences/Users/Roles/RolesLanding/components.tsx b/packages/webapp/src/containers/Preferences/Users/Roles/RolesLanding/components.tsx index dcf17b1fd..b4c344645 100644 --- a/packages/webapp/src/containers/Preferences/Users/Roles/RolesLanding/components.tsx +++ b/packages/webapp/src/containers/Preferences/Users/Roles/RolesLanding/components.tsx @@ -1,18 +1,23 @@ -// @ts-nocheck import React from 'react'; import intl from 'react-intl-universal'; - import { Intent, Menu, MenuItem, MenuDivider } from '@blueprintjs/core'; import { safeCallback } from '@/utils'; import { Icon } from '@/components'; -/** - * Context menu of roles. - */ +interface ActionsMenuPayload { + onDeleteRole: (role: { id: number; predefined: boolean }) => void; + onEditRole: (role: { id: number; predefined: boolean }) => void; +} + +interface ActionsMenuProps { + payload: ActionsMenuPayload; + row: { original: Record }; +} + export function ActionsMenu({ payload: { onDeleteRole, onEditRole }, row: { original }, -}) { +}: ActionsMenuProps) { return ( {}; +function UsersPreferences({ openDialog }: WithDialogActionsProps) { + const onChangeTabs = (currentTabId: string) => {}; return (
{ openDialog('invite-user'); }; - const onClickNewRole = () => { history.push('/preferences/roles'); }; @@ -38,4 +36,4 @@ function UsersActions({ openDialog, closeDialog }) { ); } -export default compose(withDialogActions)(UsersActions); +export const UsersActions = compose(withDialogActions)(UsersActionsInner); diff --git a/packages/webapp/src/containers/Preferences/Users/UsersDataTable.tsx b/packages/webapp/src/containers/Preferences/Users/UsersDataTable.tsx index 058c822e5..2bf77b6aa 100644 --- a/packages/webapp/src/containers/Preferences/Users/UsersDataTable.tsx +++ b/packages/webapp/src/containers/Preferences/Users/UsersDataTable.tsx @@ -86,7 +86,7 @@ function UsersDataTable({ return ( { changePreferencesPageTitle(intl.get('users')); }, [changePreferencesPageTitle]); @@ -26,4 +19,4 @@ function UsersListPreferences({ ); } -export default compose(withDashboardActions)(UsersListPreferences); +export const UsersList = compose(withDashboardActions)(UsersListPreferences); diff --git a/packages/webapp/src/containers/Preferences/Users/UsersProvider.tsx b/packages/webapp/src/containers/Preferences/Users/UsersProvider.tsx index 90888f27c..46fcded6e 100644 --- a/packages/webapp/src/containers/Preferences/Users/UsersProvider.tsx +++ b/packages/webapp/src/containers/Preferences/Users/UsersProvider.tsx @@ -1,23 +1,32 @@ -// @ts-nocheck -import React, { createContext } from 'react'; +import React, { createContext, ReactNode } from 'react'; +import type { UsersListResponse } from '@bigcapital/sdk-ts'; import { useUsers } from '@/hooks/query'; -const UsersListContext = createContext(); +interface UsersListContextValue { + users: UsersListResponse | undefined; + isUsersLoading: boolean; + isUsersFetching: boolean; +} -/** - * Users list provider. - */ -function UsersListProvider(props) { +const UsersListContext = createContext({} as UsersListContextValue); + +interface UsersListProviderProps { + children: ReactNode; +} + +function UsersListProvider({ children, ...props }: UsersListProviderProps) { const { data: users, isLoading, isFetching } = useUsers(); - - const state = { + + const state: UsersListContextValue = { isUsersLoading: isLoading, isUsersFetching: isFetching, users, }; return ( - + + {children} + ); } diff --git a/packages/webapp/src/containers/Preferences/Users/withUserPreferences.tsx b/packages/webapp/src/containers/Preferences/Users/withUserPreferences.tsx deleted file mode 100644 index a814cd94e..000000000 --- a/packages/webapp/src/containers/Preferences/Users/withUserPreferences.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; -import { CLOSE_DIALOG, OPEN_DIALOG } from '@/store/types'; - -export interface WithUserPreferencesProps { - openDialog: (name: string, payload?: Record) => void; - closeDialog: (name: string, payload?: Record) => void; -} - -export const mapDispatchToProps = (dispatch: Dispatch): WithUserPreferencesProps => ({ - openDialog: (name: string, payload?: Record) => - dispatch({ type: OPEN_DIALOG, name, payload }), - closeDialog: (name: string, payload?: Record) => - dispatch({ type: CLOSE_DIALOG, name, payload }), -}); - -export const withUserPreferences = connect(null, mapDispatchToProps); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba5000503..279010855 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -785,6 +785,9 @@ importers: specifier: ^0.28.1 version: 0.28.5 devDependencies: + '@types/flat': + specifier: ^5.0.5 + version: 5.0.5 '@vitejs/plugin-legacy': specifier: ^5.4.2 version: 5.4.3(terser@5.31.0)(vite@5.4.10(@types/node@20.19.25)(less@4.2.0)(sass@1.77.2)(terser@5.31.0)) @@ -5448,6 +5451,9 @@ packages: '@types/find-cache-dir@3.2.1': resolution: {integrity: sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==} + '@types/flat@5.0.5': + resolution: {integrity: sha512-nPLljZQKSnac53KDUDzuzdRfGI0TDb5qPrb+SrQyN3MtdQrOnGsKniHN1iYZsJEBIVQve94Y6gNz22sgISZq+Q==} + '@types/glob@7.2.0': resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} @@ -20109,6 +20115,8 @@ snapshots: '@types/find-cache-dir@3.2.1': {} + '@types/flat@5.0.5': {} + '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2