From caf232d928ea1c1a5ec04db1ac1bbcba2d0dbfd4 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 24 Nov 2025 14:18:56 +0200 Subject: [PATCH] feat: migrate from CRA to Vite for speed --- packages/webapp/craco.config.js | 17 - packages/webapp/index.html | 28 + packages/webapp/package.json | 22 +- packages/webapp/setupProxy.tsx | 11 - .../Accounts/AccountsMultiSelect.tsx | 91 +- .../Accounts/AccountsSuggestField.tsx | 100 +- .../webapp/src/components/AppIntlLoader.tsx | 18 +- .../Currencies/CurrenciesSelectList.tsx | 8 +- .../QuickPaymentMadeFormFields.tsx | 250 +- .../QuickPaymentReceiveFormFields.tsx | 234 +- packages/webapp/src/hooks/query/expenses.tsx | 2 +- .../webapp/src/hooks/query/manualJournals.tsx | 2 +- packages/webapp/src/index.tsx | 6 +- packages/webapp/src/lang/en/index.json | 3 +- packages/webapp/src/vite-env.d.ts | 2 + packages/webapp/src/wdyr.ts | 7 + packages/webapp/tsconfig.json | 9 +- packages/webapp/tsconfig.node.json | 10 + pnpm-lock.yaml | 7473 ++++------------- vercel.json | 2 +- 20 files changed, 1770 insertions(+), 6525 deletions(-) delete mode 100644 packages/webapp/craco.config.js create mode 100644 packages/webapp/index.html delete mode 100644 packages/webapp/setupProxy.tsx create mode 100644 packages/webapp/src/vite-env.d.ts create mode 100644 packages/webapp/src/wdyr.ts create mode 100644 packages/webapp/tsconfig.node.json diff --git a/packages/webapp/craco.config.js b/packages/webapp/craco.config.js deleted file mode 100644 index 4d444d065..000000000 --- a/packages/webapp/craco.config.js +++ /dev/null @@ -1,17 +0,0 @@ -const path = require('path'); - -module.exports = { - webpack: { - alias: { - '@': path.resolve(__dirname, 'src'), - }, - configure: { - resolve: { - fallback: { path: require.resolve('path-browserify') }, - }, - }, - }, - devServer: { - allowedHosts: process.env.GITPOD_HOST ? 'all' : 'auto' - }, -}; diff --git a/packages/webapp/index.html b/packages/webapp/index.html new file mode 100644 index 000000000..74fd7ed88 --- /dev/null +++ b/packages/webapp/index.html @@ -0,0 +1,28 @@ + + + + + + + + + + Bigcapital + + + +
+
+ + + + + + diff --git a/packages/webapp/package.json b/packages/webapp/package.json index 200cca730..0fe44c200 100644 --- a/packages/webapp/package.json +++ b/packages/webapp/package.json @@ -18,7 +18,6 @@ "@blueprintjs/timezone": "^4.5.43", "@casl/ability": "^5.4.3", "@casl/react": "^2.3.0", - "@craco/craco": "^5.9.0", "@emotion/css": "^11.13.4", "@emotion/react": "^11.13.3", "@react-oauth/google": "^0.12.2", @@ -36,7 +35,7 @@ "@tiptap/starter-kit": "2.1.13", "@types/js-money": "^0.6.1", "@types/lodash": "^4.14.172", - "@types/node": "^14.14.9", + "@types/node": "^20.16.10", "@types/ramda": "^0.28.14", "@types/react": "18.3.4", "@types/react-body-classname": "^1.1.7", @@ -67,7 +66,6 @@ "formik": "^2.2.5", "helmet": "^3.21.0", "history": "4.10.1", - "http-proxy-middleware": "^1.0.0", "js-cookie": "2.2.1", "js-money": "^0.6.3", "lodash": "^4.17.15", @@ -81,16 +79,13 @@ "query-string": "^7.1.1", "ramda": "^0.27.1", "react": "^18.2.0", - "react-app-polyfill": "^1.0.6", "react-body-classname": "^1.3.1", "react-colorful": "^5.6.1", "react-content-loader": "^6.0.1", - "react-dev-utils": "^11.0.4", "react-dom": "^18.2.0", "react-dropzone": "^11.0.1", "react-dropzone-esm": "^15.0.1", "react-error-boundary": "^3.0.2", - "react-error-overlay": "^6.0.9", "react-helmet": "^6.1.0", "react-hotkeys-hook": "^3.0.3", "react-intl-universal": "^2.4.7", @@ -102,7 +97,6 @@ "react-router": "5.3.4", "react-router-breadcrumbs-hoc": "^3.2.10", "react-router-dom": "^5.3.3", - "react-scripts": "5.0.1", "react-scroll-sync": "^0.7.1", "react-scrollbars-custom": "^4.0.21", "react-sortablejs": "^2.0.11", @@ -129,17 +123,19 @@ "typescript": "^4.8.3", "yup": "^0.28.1" }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "eslint-config-react-app": "^7.0.1", + "vite": "^5.1.6" + }, "scripts": { - "dev": "cross-env PORT=4000 craco start", - "build": "craco build", + "dev": "cross-env PORT=4000 vite", + "build": "vite build", + "preview": "cross-env PORT=4173 vite preview", "typecheck": "tsc --noEmit", "test": "node scripts/test.js", "storybook": "start-storybook -p 6006" }, - "proxy": "http://localhost:3000/", - "resolutions": { - "react-error-overlay": "6.0.9" - }, "eslintConfig": { "extends": [ "react-app", diff --git a/packages/webapp/setupProxy.tsx b/packages/webapp/setupProxy.tsx deleted file mode 100644 index 70ca83467..000000000 --- a/packages/webapp/setupProxy.tsx +++ /dev/null @@ -1,11 +0,0 @@ -const proxy = require('http-proxy-middleware'); - -module.exports = function(app) { - app.use( - '/api', - proxy({ - target: 'http://localhost:3000', - changeOrigin: true, - }) - ); -}; \ No newline at end of file diff --git a/packages/webapp/src/components/Accounts/AccountsMultiSelect.tsx b/packages/webapp/src/components/Accounts/AccountsMultiSelect.tsx index c7b2d1e01..fda4588b3 100644 --- a/packages/webapp/src/components/Accounts/AccountsMultiSelect.tsx +++ b/packages/webapp/src/components/Accounts/AccountsMultiSelect.tsx @@ -1,13 +1,43 @@ -// @ts-nocheck import React from 'react'; import { MenuItem } from '@blueprintjs/core'; +import intl from 'react-intl-universal'; import { FMultiSelect } from '../Forms'; import { accountPredicate } from './_components'; -import { MenuItemNestedText } from '../Menu'; import { usePreprocessingAccounts } from './_hooks'; +import { DialogsName } from '@/constants/dialogs'; +import { useDialogActions } from '@/hooks/state/dashboard'; +import { SelectOptionProps } from '@blueprintjs-formik/select'; + +interface Account { + id: number; + name: string; + code: string; + account_level: number; + account_type?: string; + account_parent_type?: string; + account_root_type?: string; + account_normal?: string; +} + +interface AccountSelect extends Account, SelectOptionProps { } + +type MultiSelectProps = React.ComponentProps; + +interface AccountsMultiSelectProps extends Omit { + items: AccountSelect[]; + allowCreate?: boolean; + filterByRootTypes?: string[]; + filterByParentTypes?: string[]; + filterByTypes?: string[]; + filterByNormal?: string[]; +} // Create new account renderer. -const createNewItemRenderer = (query, active, handleClick) => { +const createNewItemRenderer = ( + query: string, + active: boolean, + handleClick: (event: React.MouseEvent) => void, +): React.ReactElement => { return ( { ); }; -/** - * Default account item renderer of the list. - * @returns {JSX.Element} - */ -const accountRenderer = ( - item, - { handleClick, modifiers, query }, - { isSelected }, -) => { - if (!modifiers.matchesPredicate) { - return null; - } - return ( - } - key={item.id} - onClick={handleClick} - icon={isSelected ? 'tick' : 'blank'} - /> - ); -}; - // Create new item from the given query string. -const createNewItemFromQuery = (name) => ({ name }); +const createNewItemFromQuery = (query: string): SelectOptionProps => ({ + label: query, + value: query, + id: 0, +}); /** * Accounts multi-select field binded with Formik form. @@ -59,27 +69,32 @@ export function AccountsMultiSelect({ filterByNormal, ...rest -}) { +}: AccountsMultiSelectProps): React.ReactElement { + const { openDialog } = useDialogActions(); + // Filters accounts based on filter props. const filteredAccounts = usePreprocessingAccounts(items, { - filterByParentTypes, - filterByTypes, - filterByNormal, - filterByRootTypes, + filterByParentTypes: filterByParentTypes || [], + filterByTypes: filterByTypes || [], + filterByNormal: filterByNormal || [], + filterByRootTypes: filterByRootTypes || [], }); // Maybe inject new item props to select component. - const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null; + const maybeCreateNewItemRenderer = allowCreate + ? createNewItemRenderer + : undefined; const maybeCreateNewItemFromQuery = allowCreate ? createNewItemFromQuery - : null; + : undefined; // Handles the create item click. - const handleCreateItemClick = () => { + const handleCreateItemClick = (): void => { openDialog(DialogsName.AccountForm); }; return ( - + {...rest} items={filteredAccounts} valueAccessor={'id'} textAccessor={'name'} @@ -87,11 +102,9 @@ export function AccountsMultiSelect({ tagAccessor={'name'} popoverProps={{ minimal: true }} itemPredicate={accountPredicate} - itemRenderer={accountRenderer} createNewItemRenderer={maybeCreateNewItemRenderer} createNewItemFromQuery={maybeCreateNewItemFromQuery} onCreateItemSelect={handleCreateItemClick} - {...rest} /> ); } diff --git a/packages/webapp/src/components/Accounts/AccountsSuggestField.tsx b/packages/webapp/src/components/Accounts/AccountsSuggestField.tsx index 4680d150a..7b6405d48 100644 --- a/packages/webapp/src/components/Accounts/AccountsSuggestField.tsx +++ b/packages/webapp/src/components/Accounts/AccountsSuggestField.tsx @@ -1,15 +1,18 @@ // @ts-nocheck -import React, { useState, useCallback, useEffect, useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import * as R from 'ramda'; import intl from 'react-intl-universal'; import classNames from 'classnames'; import { MenuItem } from '@blueprintjs/core'; -import { Suggest } from '@blueprintjs/select'; import { CLASSES } from '@/constants/classes'; import { DialogsName } from '@/constants/dialogs'; -import { MenuItemNestedText, FormattedMessage as T } from '@/components'; +import { + FSuggest, + MenuItemNestedText, + FormattedMessage as T, +} from '@/components'; import { nestedArrayToflatten, filterAccountsByQuery } from '@/utils'; import withDialogActions from '@/containers/Dialog/withDialogActions'; @@ -33,14 +36,6 @@ const createNewItemFromQuery = (name) => { }; }; -// Handle input value renderer. -const handleInputValueRenderer = (inputValue) => { - if (inputValue) { - return inputValue.name.toString(); - } - return ''; -}; - // Filters accounts items. const filterAccountsPredicater = (query, account, _index, exactMatch) => { const normalizedTitle = account.name.toLowerCase(); @@ -62,11 +57,7 @@ function AccountsSuggestFieldRoot({ // #ownProps accounts, - initialAccountId, - selectedAccountId, defaultSelectText = intl.formatMessage({ id: 'select_account' }), - popoverFill = false, - onAccountSelected, filterByParentTypes = [], filterByTypes = [], @@ -81,67 +72,14 @@ function AccountsSuggestFieldRoot({ () => nestedArrayToflatten(accounts), [accounts], ); - - // Filters accounts based on filter props. - const filteredAccounts = useMemo(() => { - let filteredAccounts = filterAccountsByQuery(flattenAccounts, { - filterByRootTypes, - filterByParentTypes, - filterByTypes, - filterByNormal, - }); - return filteredAccounts; - }, [ - flattenAccounts, - filterByRootTypes, - filterByParentTypes, - filterByTypes, - filterByNormal, - ]); - - // Find initial account object to set it as default account in initial render. - const initialAccount = useMemo( - () => filteredAccounts.find((a) => a.id === initialAccountId), - [initialAccountId, filteredAccounts], - ); - - const [selectedAccount, setSelectedAccount] = useState( - initialAccount || null, - ); - - useEffect(() => { - if (typeof selectedAccountId !== 'undefined') { - const account = selectedAccountId - ? filteredAccounts.find((a) => a.id === selectedAccountId) - : null; - setSelectedAccount(account); - } - }, [selectedAccountId, filteredAccounts, setSelectedAccount]); - - // Account item of select accounts field. - const accountItem = useCallback((item, { handleClick, modifiers, query }) => { - return ( - } - label={item.code} - key={item.id} - onClick={handleClick} - /> - ); - }, []); - - const handleAccountSelect = useCallback( - (account) => { - if (account.id) { - setSelectedAccount({ ...account }); - onAccountSelected && onAccountSelected(account); - } else { + const handleCreateItemSelect = useCallback( + (item) => { + if (!item.id) { openDialog(DialogsName.AccountForm); } }, - [setSelectedAccount, onAccountSelected, openDialog], + [openDialog], ); - // Maybe inject new item props to select component. const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null; const maybeCreateNewItemFromQuery = allowCreate @@ -149,21 +87,15 @@ function AccountsSuggestFieldRoot({ : null; return ( - } />} - itemRenderer={accountItem} - itemPredicate={filterAccountsPredicater} - onItemSelect={handleAccountSelect} - selectedItem={selectedAccount} + onCreateItemSelect={handleCreateItemSelect} + valueAccessor="id" + textAccessor="name" + labelAccessor="code" inputProps={{ placeholder: defaultSelectText }} - resetOnClose={true} - fill={true} + resetOnClose popoverProps={{ minimal: true, boundary: 'window' }} - inputValueRenderer={handleInputValueRenderer} - className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, { - [CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill, - })} createNewItemRenderer={maybeCreateNewItemRenderer} createNewItemFromQuery={maybeCreateNewItemFromQuery} {...suggestProps} diff --git a/packages/webapp/src/components/AppIntlLoader.tsx b/packages/webapp/src/components/AppIntlLoader.tsx index 419a283c6..98b3006a9 100644 --- a/packages/webapp/src/components/AppIntlLoader.tsx +++ b/packages/webapp/src/components/AppIntlLoader.tsx @@ -36,15 +36,19 @@ function getCurrentLocal() { /** * Loads the localization data of the given locale. */ -function loadLocales(currentLocale) { - return import(`../lang/${currentLocale}/index.json`); +async function loadLocales(currentLocale) { + return await import(`../lang/${currentLocale}/index.json`).then( + (module) => module.default, + ); } /** * Loads the localization data of yup validation library. */ -function loadYupLocales(currentLocale) { - return import(`../lang/${currentLocale}/locale`); +async function loadYupLocales(currentLocale) { + return await import(`../lang/${currentLocale}/locale.tsx`).then( + (module) => module.locale, + ); } /** @@ -109,11 +113,11 @@ function useAppYupLoadLocales(currentLocale) { React.useEffect(() => { loadYupLocales(currentLocale) - .then(({ locale }) => { - setLocale(locale); + .then((results) => { + setLocale(results); setIsLoading(false); }) - .then(() => {}); + .then(() => { }); }, [currentLocale, stopLoading]); // Watches the valiue to start/stop splash screen. diff --git a/packages/webapp/src/components/Currencies/CurrenciesSelectList.tsx b/packages/webapp/src/components/Currencies/CurrenciesSelectList.tsx index af531e2fa..70aa690d0 100644 --- a/packages/webapp/src/components/Currencies/CurrenciesSelectList.tsx +++ b/packages/webapp/src/components/Currencies/CurrenciesSelectList.tsx @@ -6,10 +6,14 @@ import { Select } from '@blueprintjs/select'; export function CurrenciesSelectList({ selectProps, onItemSelect, className }) { const currencies = [ { + id: 'USD', + code: 'USD', name: 'USD US dollars', - key: 'USD', + }, + { + id: 'CAD', + code: 'CAD', name: 'CAD Canadian dollars', - key: 'CAD', }, ]; diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.tsx index bcb0f473d..5e166caaf 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.tsx @@ -2,20 +2,12 @@ import React from 'react'; import intl from 'react-intl-universal'; import styled from 'styled-components'; -import { FastField, ErrorMessage, useFormikContext } from 'formik'; +import { FastField, useFormikContext } from 'formik'; import { isEqual } from 'lodash'; -import { - Classes, - FormGroup, - InputGroup, - TextArea, - Position, - ControlGroup, -} from '@blueprintjs/core'; +import { Classes, Position, ControlGroup } from '@blueprintjs/core'; import { useAutofocus } from '@/hooks'; import classNames from 'classnames'; import { CLASSES, ACCOUNT_TYPE, Features } from '@/constants'; -import { DateInput } from '@blueprintjs/datetime'; import { FieldRequiredHint, @@ -31,13 +23,13 @@ import { ExchangeRateMutedField, BranchSelect, BranchSelectButton, + FFormGroup, + FInputGroup, + FDateInput, + FTextArea, + FMoneyInputGroup, } from '@/components'; -import { - inputIntent, - momentFormatter, - tansformDateValue, - handleDateChange, -} from '@/utils'; +import { inputIntent, momentFormatter } from '@/utils'; import { useSetPrimaryBranchToForm } from './utils'; import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider'; @@ -66,7 +58,7 @@ function QuickPaymentMadeFormFields({ - } className={classNames('form-group--select-list', Classes.FILL)} > @@ -76,83 +68,45 @@ function QuickPaymentMadeFormFields({ input={BranchSelectButton} popoverProps={{ minimal: true }} /> - + + + {/* ------------- Vendor name ------------- */} - {/* ------------- Vendor name ------------- */} - - {({ from, field, meta: { error, touched } }) => ( - } - className={classNames('form-group--select-list', CLASSES.FILL)} - labelInfo={} - intent={inputIntent({ error, touched })} - helperText={} - > - - - )} - + }> + + + + {/* ------------ Payment number. ------------ */} - {/* ------------ Payment number. ------------ */} - - {({ form, field, meta: { error, touched } }) => ( - } - className={('form-group--payment_number', CLASSES.FILL)} - intent={inputIntent({ error, touched })} - helperText={} - > - - - )} - + }> + + - {/*------------ Amount Received -----------*/} - - {({ - form: { values, setFieldValue }, - field: { value }, - meta: { error, touched }, - }) => ( - } - labelInfo={} - className={classNames('form-group--payment_amount', CLASSES.FILL)} - intent={inputIntent({ error, touched })} - helperText={} - > - - - { - setFieldValue('amount', amount); - }} - intent={inputIntent({ error, touched })} - inputRef={(ref) => (paymentMadeFieldRef.current = ref)} - /> - - - )} - + {/*------------ Amount Received -----------*/} + } + labelInfo={} + className={classNames('form-group--payment_amount', CLASSES.FILL)} + > + + + + (paymentMadeFieldRef.current = ref)} + /> + + {/*------------ exchange rate -----------*/} @@ -165,95 +119,67 @@ function QuickPaymentMadeFormFields({ exchangeRate={values.exchange_rate} /> + {/* ------------- Payment date ------------- */} - - {({ form, field: { value }, meta: { error, touched } }) => ( - } - labelInfo={} - className={classNames('form-group--select-list', CLASSES.FILL)} - intent={inputIntent({ error, touched })} - helperText={} - > - { - form.setFieldValue('payment_date', formattedDate); - })} - popoverProps={{ position: Position.BOTTOM, minimal: true }} - inputProps={{ - leftIcon: , - }} - /> - - )} - + } + labelInfo={} + className={classNames('form-group--select-list', CLASSES.FILL)} + > + , + }} + /> + {/* ------------ payment account ------------ */} - - {({ form, field: { value }, meta: { error, touched } }) => ( - } - className={classNames( - 'form-group--payment_account_id', - 'form-group--select-list', - CLASSES.FILL, - )} - labelInfo={} - intent={inputIntent({ error, touched })} - helperText={} - > - - form.setFieldValue('payment_account_id', id) - } - inputProps={{ - placeholder: intl.get('select_account'), - }} - filterByTypes={[ - ACCOUNT_TYPE.CASH, - ACCOUNT_TYPE.BANK, - ACCOUNT_TYPE.OTHER_CURRENT_ASSET, - ]} - /> - - )} - + } + > + + form.setFieldValue('payment_account_id', id) + } + inputProps={{ + placeholder: intl.get('select_account'), + }} + filterByTypes={[ + ACCOUNT_TYPE.CASH, + ACCOUNT_TYPE.BANK, + ACCOUNT_TYPE.OTHER_CURRENT_ASSET, + ]} + /> + + {/* ------------ Reference No. ------------ */} - - {({ form, field, meta: { error, touched } }) => ( - } - className={classNames('form-group--reference', CLASSES.FILL)} - intent={inputIntent({ error, touched })} - helperText={} - > - - - )} - + } + className={classNames('form-group--reference', CLASSES.FILL)} + > + + + {/* --------- Statement --------- */} - - {({ form, field, meta: { error, touched } }) => ( - } - className={'form-group--statement'} - > -