diff --git a/src/components/App.test.tsx b/src/components/App.test.tsx
index 7333f46ec..010791f61 100644
--- a/src/components/App.test.tsx
+++ b/src/components/App.test.tsx
@@ -1,9 +1,15 @@
import { render, screen, act } from '../testUtils'
import user from '@testing-library/user-event'
import * as apiMock from '../libs/JmWalletApi'
+import * as loadersMock from './loaders/DataLoaders'
import App from './App'
+jest.mock('./loaders/DataLoaders', () => ({
+ ...jest.requireActual('./loaders/DataLoaders'),
+ allWalletsLoader: jest.fn(),
+}))
+
jest.mock('../libs/JmWalletApi', () => ({
...jest.requireActual('../libs/JmWalletApi'),
getGetinfo: jest.fn(),
@@ -15,6 +21,12 @@ describe('', () => {
const neverResolvingPromise = new Promise(() => {})
;(apiMock.getGetinfo as jest.Mock).mockResolvedValue(neverResolvingPromise)
;(apiMock.getSession as jest.Mock).mockResolvedValue(neverResolvingPromise)
+ ;(loadersMock.allWalletsLoader as jest.Mock).mockReturnValue(
+ Promise.resolve({
+ ok: true,
+ wallets: [],
+ }),
+ )
})
it('should display Onboarding screen initially', async () => {
diff --git a/src/components/App.tsx b/src/components/App.tsx
index c21c84ec6..02589a842 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -39,55 +39,144 @@ import Send from './Send'
import RescanChain from './RescanChain'
import Settings from './Settings'
import Wallets from './Wallets'
+import { allWalletsLoader } from './loaders/DataLoaders'
const DevSetupPage = lazy(() => import('./DevSetupPage'))
-export default function App() {
- const { t } = useTranslation()
- const settings = useSettings()
- const currentWallet = useCurrentWallet()
- const setCurrentWallet = useSetCurrentWallet()
- const clearCurrentWallet = useClearCurrentWallet()
- const reloadCurrentWalletInfo = useReloadCurrentWalletInfo()
- const serviceInfo = useServiceInfo()
- const sessionConnectionError = useSessionConnectionError()
- const [reloadingWalletInfoCounter, setReloadingWalletInfoCounter] = useState(0)
- const isReloadingWalletInfo = useMemo(() => reloadingWalletInfoCounter > 0, [reloadingWalletInfoCounter])
-
- const startWallet = useCallback(
- (walletFileName: Api.WalletFileName, auth: Api.ApiAuthContext) => {
- setSession({ walletFileName, auth })
- setCurrentWallet({ walletFileName, token: auth.token })
+const router3 = (startWallet: any, sessionConnectionError: any, currentWallet: any, stopWallet: any, t: any) =>
+ createBrowserRouter([
+ {
+ id: 'base',
+ element: (
+ <>
+
+
+
+
+
+ >
+ ),
+ errorElement: ,
+ children: [
+ {
+ id: 'error-boundary',
+ element: (
+
+
+
+ ),
+ errorElement: (
+
+
+
+ ),
+ children: [
+ // Routes that are displayed even if the connection to the backend is down
+ {
+ id: 'create-wallet',
+ path: routes.createWallet,
+ loader: allWalletsLoader,
+ element: ,
+ },
+ sessionConnectionError && {
+ id: '404',
+ path: '*',
+ element: (
+
+ {t('app.alert_no_connection', { connectionError: sessionConnectionError.message })}.
+
+ ),
+ },
+ // Routes that are displayed only if the backend is reachable
+ !sessionConnectionError && [
+ {
+ id: 'wallets',
+ path: routes.home,
+ loader: allWalletsLoader,
+ element: ,
+ },
+ {
+ id: 'import-wallet',
+ path: routes.importWallet,
+ element: ,
+ },
+ currentWallet && [
+ {
+ id: 'wallet',
+ path: routes.wallet,
+ element: ,
+ },
+ {
+ id: 'jam',
+ path: routes.jam,
+ element: ,
+ },
+ {
+ id: 'send',
+ path: routes.send,
+ element: ,
+ },
+ {
+ id: 'earn',
+ path: routes.earn,
+ element: ,
+ },
+ {
+ id: 'receive',
+ path: routes.receive,
+ element: ,
+ },
+ {
+ id: 'rescan',
+ path: routes.rescanChain,
+ element: ,
+ },
+ {
+ id: 'settings',
+ path: routes.settings,
+ element: ,
+ },
+ ],
+ isDebugFeatureEnabled('errorExamplePage') && {
+ id: 'error-example',
+ path: routes.__errorExample,
+ element: ,
+ },
+ isDebugFeatureEnabled('devSetupPage') && {
+ id: 'dev-env',
+ path: routes.__devSetup,
+ element: (
+ }>
+
+
+ ),
+ },
+ {
+ id: '404',
+ path: '*',
+ element: ,
+ },
+ ],
+ ],
+ },
+ ],
},
- [setCurrentWallet],
- )
+ ])
- const stopWallet = useCallback(() => {
- clearCurrentWallet()
- clearSession()
- }, [clearCurrentWallet])
+// passing in anything here causes multiple renders. Why?
+// actually it's not the passing in, it's the use of the function
+// this works fine in photo voice...
+const router2 = createBrowserRouter([
+ {
+ id: 'wallets',
+ path: routes.home,
+ loader: allWalletsLoader,
+ element:
Wallets
,
+ // element:
+ },
+])
- const reloadWalletInfo = useCallback(
- (delay: Milliseconds) => {
- setReloadingWalletInfoCounter((current) => current + 1)
- console.info('Reloading wallet info...')
- return new Promise((resolve, reject) =>
- setTimeout(() => {
- const abortCtrl = new AbortController()
- reloadCurrentWalletInfo
- .reloadAll({ signal: abortCtrl.signal })
- .then((result) => resolve(result))
- .catch((error) => reject(error))
- .finally(() => {
- console.info('Finished reloading wallet info.')
- setReloadingWalletInfoCounter((current) => current - 1)
- })
- }, delay),
- )
- },
- [reloadCurrentWalletInfo],
- )
-
- const router = createBrowserRouter(
+const router = (startWallet: any, sessionConnectionError: any, currentWallet: any, stopWallet: any, t: any) =>
+ createBrowserRouter(
createRoutesFromElements(
}
/>
@@ -144,6 +234,7 @@ export default function App() {
}
/>
reloadingWalletInfoCounter > 0, [reloadingWalletInfoCounter])
+
+ const startWallet = useCallback(
+ (walletFileName: Api.WalletFileName, auth: Api.ApiAuthContext) => {
+ setSession({ walletFileName, auth })
+ setCurrentWallet({ walletFileName, token: auth.token })
+ },
+ [setCurrentWallet],
+ )
+
+ const stopWallet = useCallback(() => {
+ clearCurrentWallet()
+ clearSession()
+ }, [clearCurrentWallet])
+
+ const reloadWalletInfo = useCallback(
+ (delay: Milliseconds) => {
+ setReloadingWalletInfoCounter((current) => current + 1)
+ console.info('Reloading wallet info...')
+ return new Promise((resolve, reject) =>
+ setTimeout(() => {
+ const abortCtrl = new AbortController()
+ reloadCurrentWalletInfo
+ .reloadAll({ signal: abortCtrl.signal })
+ .then((result) => resolve(result))
+ .catch((error) => reject(error))
+ .finally(() => {
+ console.info('Finished reloading wallet info.')
+ setReloadingWalletInfoCounter((current) => current - 1)
+ })
+ }, delay),
+ )
+ },
+ [reloadCurrentWalletInfo],
+ )
+
if (settings.showOnboarding === true) {
return (
@@ -217,7 +354,9 @@ export default function App() {
'jm-maker-running': serviceInfo?.makerRunning === true,
})}
>
-
+ {/* */}
+ {/* */}
+
>
diff --git a/src/components/CreateWallet.test.tsx b/src/components/CreateWallet.test.tsx
index 967d68880..40c8a0eef 100644
--- a/src/components/CreateWallet.test.tsx
+++ b/src/components/CreateWallet.test.tsx
@@ -15,6 +15,16 @@ jest.mock('../libs/JmWalletApi', () => ({
postWalletCreate: jest.fn(),
}))
+const mockedUseNavigation = jest.fn()
+const mockUseLoaderData = jest.fn()
+jest.mock('react-router-dom', () => {
+ return {
+ ...jest.requireActual('react-router-dom'),
+ useNavigation: () => mockedUseNavigation,
+ useLoaderData: () => mockUseLoaderData,
+ }
+})
+
const NOOP = () => {}
describe('', () => {
diff --git a/src/components/WalletCreationForm.tsx b/src/components/WalletCreationForm.tsx
index db7ba6e50..1b9176610 100644
--- a/src/components/WalletCreationForm.tsx
+++ b/src/components/WalletCreationForm.tsx
@@ -2,9 +2,11 @@ import { useCallback } from 'react'
import * as rb from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import { Formik, FormikErrors } from 'formik'
+import { useLoaderData, useNavigation } from 'react-router-dom'
import Sprite from './Sprite'
import { JM_WALLET_FILE_EXTENSION, sanitizeWalletName } from '../utils'
import styles from './WalletCreationForm.module.css'
+import { AllWalletsLoaderResponse } from './loaders/DataLoaders'
export interface CreateWalletFormValues {
walletName: string
@@ -37,6 +39,8 @@ const WalletCreationForm = ({
onSubmit,
}: WalletCreationFormProps) => {
const { t, i18n } = useTranslation()
+ const getWalletAllResponse = useLoaderData() as AllWalletsLoaderResponse
+ const navigation = useNavigation()
const validate = useCallback(
(values: CreateWalletFormValues) => {
@@ -44,6 +48,12 @@ const WalletCreationForm = ({
if (!values.walletName || !validateWalletName(values.walletName)) {
errors.walletName = t('create_wallet.feedback_invalid_wallet_name')
}
+ if (
+ navigation.state === 'idle' &&
+ getWalletAllResponse?.existingWallets?.wallets?.includes(`${values.walletName}.jmdat`)
+ ) {
+ errors.walletName = t('create_wallet.feedback_wallet_name_already_exists')
+ }
if (!values.password) {
errors.password = t('create_wallet.feedback_invalid_password')
}
@@ -52,7 +62,7 @@ const WalletCreationForm = ({
}
return errors
},
- [t],
+ [getWalletAllResponse?.existingWallets?.wallets, navigation.state, t],
)
return (
diff --git a/src/components/Wallets.test.tsx b/src/components/Wallets.test.tsx
index b083bc49b..e26e74bb3 100644
--- a/src/components/Wallets.test.tsx
+++ b/src/components/Wallets.test.tsx
@@ -1,11 +1,18 @@
import { BrowserRouter } from 'react-router-dom'
-import { act, render, screen, waitFor, waitForElementToBeRemoved } from '../testUtils'
+import { act, render, screen, waitFor } from '../testUtils'
import user from '@testing-library/user-event'
import * as apiMock from '../libs/JmWalletApi'
+import * as loadersMock from './loaders/DataLoaders'
import Wallets from './Wallets'
import { CurrentWallet } from '../context/WalletContext'
+import { t } from 'i18next'
+
+jest.mock('./loaders/DataLoaders', () => ({
+ ...jest.requireActual('./loaders/DataLoaders'),
+ allWalletsLoader: jest.fn(),
+}))
jest.mock('../libs/JmWalletApi', () => ({
...jest.requireActual('../libs/JmWalletApi'),
@@ -16,11 +23,22 @@ jest.mock('../libs/JmWalletApi', () => ({
getWalletLock: jest.fn(),
}))
-const mockedNavigate = jest.fn()
+const mockUseNavigate = jest.fn()
+let mockUseNavigation = {
+ state: 'loading',
+}
+let mockUseLoaderData = {
+ existingWallets: {
+ wallets: [''],
+ },
+ existingWalletsError: '',
+}
jest.mock('react-router-dom', () => {
return {
...jest.requireActual('react-router-dom'),
- useNavigate: () => mockedNavigate,
+ useNavigate: () => mockUseNavigate,
+ useNavigation: () => mockUseNavigation,
+ useLoaderData: () => mockUseLoaderData,
}
})
@@ -41,6 +59,16 @@ describe('', () => {
;(apiMock.getSession as jest.Mock).mockReturnValue(neverResolvingPromise)
;(apiMock.getGetinfo as jest.Mock).mockReturnValue(neverResolvingPromise)
;(apiMock.getWalletAll as jest.Mock).mockReturnValue(neverResolvingPromise)
+ // ;jest.mock('react-router-dom', () => {
+ // return {
+ // ...jest.requireActual('react-router-dom'),
+ // useNavigate: () => mockedUseNavigate,
+ // useNavigation: () => ({
+ // state: 'idle'
+ // }),
+ // useLoaderData: () => mockUseLoaderData,
+ // }
+ // })
})
it('should display loading indicator while fetching data', async () => {
@@ -67,6 +95,12 @@ describe('', () => {
ok: false,
}),
)
+ mockUseLoaderData = {
+ existingWallets: {
+ wallets: [],
+ },
+ existingWalletsError: t('wallets.error_loading_failed'),
+ }
await act(async () => setup({}))
@@ -147,18 +181,21 @@ describe('', () => {
}),
}),
)
- ;(apiMock.getWalletAll as jest.Mock).mockReturnValue(
- Promise.resolve({
- ok: true,
- json: () => Promise.resolve({ wallets: ['wallet0.jmdat', 'wallet1.jmdat'] }),
- }),
- )
;(apiMock.getGetinfo as jest.Mock).mockReturnValue(
Promise.resolve({
ok: true,
json: () => Promise.resolve({ version: '0.9.10dev' }),
}),
)
+ mockUseLoaderData = {
+ existingWallets: {
+ wallets: ['wallet0.jmdat', 'wallet1.jmdat'],
+ },
+ existingWalletsError: '',
+ }
+ mockUseNavigation = {
+ state: 'idle',
+ }
await act(async () => setup({}))
@@ -243,6 +280,15 @@ describe('', () => {
}),
}),
)
+ mockUseLoaderData = {
+ existingWallets: {
+ wallets: [dummyWalletFileName],
+ },
+ existingWalletsError: '',
+ }
+ mockUseNavigation = {
+ state: 'idle',
+ }
await act(async () => setup({}))
@@ -260,7 +306,7 @@ describe('', () => {
token: dummyToken,
refresh_token: dummyToken,
})
- expect(mockedNavigate).toHaveBeenCalledWith('/wallet')
+ expect(mockUseNavigate).toHaveBeenCalledWith('/wallet')
})
it('should add alert if unlocking of inactive wallet fails', async () => {
@@ -290,6 +336,15 @@ describe('', () => {
json: () => Promise.resolve({ message: apiErrorMessage }),
}),
)
+ mockUseLoaderData = {
+ existingWallets: {
+ wallets: [dummyWalletFileName],
+ },
+ existingWalletsError: '',
+ }
+ mockUseNavigation = {
+ state: 'idle',
+ }
await act(async () => setup({}))
@@ -303,7 +358,7 @@ describe('', () => {
await user.click(unlockWalletButton)
expect(mockStartWallet).not.toHaveBeenCalled()
- expect(mockedNavigate).not.toHaveBeenCalled()
+ expect(mockUseNavigate).not.toHaveBeenCalled()
expect(screen.getByText(apiErrorMessage.replace('Wallet', dummyWalletFileName))).toBeInTheDocument()
})
@@ -333,6 +388,15 @@ describe('', () => {
json: () => Promise.resolve({ walletname: dummyWalletFileName, already_locked: false }),
}),
)
+ mockUseLoaderData = {
+ existingWallets: {
+ wallets: [dummyWalletFileName],
+ },
+ existingWalletsError: '',
+ }
+ mockUseNavigation = {
+ state: 'idle',
+ }
await act(async () =>
setup({
@@ -382,6 +446,15 @@ describe('', () => {
json: () => Promise.resolve({ message: apiErrorMessage }),
}),
)
+ mockUseLoaderData = {
+ existingWallets: {
+ wallets: [dummyWalletFileName],
+ },
+ existingWalletsError: '',
+ }
+ mockUseNavigation = {
+ state: 'idle',
+ }
await act(async () =>
setup({
@@ -437,6 +510,15 @@ describe('', () => {
json: () => Promise.resolve({ walletname: dummyWalletFileName, already_locked: false }),
}),
)
+ mockUseLoaderData = {
+ existingWallets: {
+ wallets: [dummyWalletFileName],
+ },
+ existingWalletsError: '',
+ }
+ mockUseNavigation = {
+ state: 'idle',
+ }
await act(async () =>
setup({
diff --git a/src/components/Wallets.tsx b/src/components/Wallets.tsx
index 54ad88175..2ede95ad3 100644
--- a/src/components/Wallets.tsx
+++ b/src/components/Wallets.tsx
@@ -1,5 +1,5 @@
import { useMemo, useEffect, useCallback, useState } from 'react'
-import { Link, useNavigate } from 'react-router-dom'
+import { Link, useLoaderData, useNavigate, useNavigation } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import * as rb from 'react-bootstrap'
import classNames from 'classnames'
@@ -7,17 +7,14 @@ import Sprite from './Sprite'
import Alert from './Alert'
import Wallet from './Wallet'
import PageTitle from './PageTitle'
-import { useServiceInfo, useReloadServiceInfo } from '../context/ServiceInfoContext'
+import { useServiceInfo } from '../context/ServiceInfoContext'
import { walletDisplayName } from '../utils'
import * as Api from '../libs/JmWalletApi'
import { routes } from '../constants/routes'
import { ConfirmModal } from './Modal'
import { isFeatureEnabled } from '../constants/features'
import { CurrentWallet } from '../context/WalletContext'
-
-function arrayEquals(a: any, b: any) {
- return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((val, index) => val === b[index])
-}
+import { AllWalletsLoaderResponse } from './loaders/DataLoaders'
function sortWallets(
wallets: Api.WalletFileName[],
@@ -39,10 +36,9 @@ interface WalletsProps {
export default function Wallets({ currentWallet, startWallet, stopWallet }: WalletsProps) {
const { t } = useTranslation()
const navigate = useNavigate()
+ const navigation = useNavigation()
const serviceInfo = useServiceInfo()
- const reloadServiceInfo = useReloadServiceInfo()
- const [walletList, setWalletList] = useState(null)
- const [isLoading, setIsLoading] = useState(true)
+ const getWalletAllResponse = useLoaderData() as AllWalletsLoaderResponse
const [unlockingWalletFileName, setUnlockWalletFileName] = useState()
const isUnlocking = useMemo(() => unlockingWalletFileName !== undefined, [unlockingWalletFileName])
const [alert, setAlert] = useState()
@@ -161,53 +157,28 @@ export default function Wallets({ currentWallet, startWallet, stopWallet }: Wall
}, [currentWallet, lockWallet])
useEffect(() => {
- if (walletList && serviceInfo) {
- const sortedWalletList = sortWallets(walletList, serviceInfo.walletFileName)
- if (!arrayEquals(walletList, sortedWalletList)) {
- setWalletList(sortedWalletList)
- }
- }
- }, [serviceInfo, walletList])
-
- useEffect(() => {
- const abortCtrl = new AbortController()
-
- setIsLoading(true)
- const loadingServiceInfo = reloadServiceInfo({ signal: abortCtrl.signal })
-
- const loadingWallets = Api.getWalletAll({ signal: abortCtrl.signal })
- .then((res) => (res.ok ? res.json() : Api.Helper.throwError(res, t('wallets.error_loading_failed'))))
- .then((data) => sortWallets(data.wallets || [], currentWallet?.walletFileName))
- .then((sortedWalletList) => {
- if (abortCtrl.signal.aborted) return
-
- setWalletList(sortedWalletList)
-
- if (currentWallet && sortedWalletList.length > 1) {
- setAlert({
- variant: 'info',
- message: t('wallets.alert_wallet_open', { currentWalletName: currentWallet.displayName }),
- dismissible: false,
- })
- }
+ if (currentWallet && getWalletAllResponse?.existingWallets?.wallets?.length > 1) {
+ setAlert({
+ variant: 'info',
+ message: t('wallets.alert_wallet_open', { currentWalletName: currentWallet.displayName }),
+ dismissible: false,
})
+ }
- Promise.all([loadingServiceInfo, loadingWallets])
- .catch((err) => {
- const message = err.message || t('wallets.error_loading_failed')
- !abortCtrl.signal.aborted && setAlert({ variant: 'danger', message })
- })
- .finally(() => !abortCtrl.signal.aborted && setIsLoading(false))
-
- return () => abortCtrl.abort()
- }, [currentWallet, reloadServiceInfo, t])
+ if (getWalletAllResponse?.existingWalletsError) {
+ const message = getWalletAllResponse?.existingWalletsError || t('wallets.error_loading_failed')
+ setAlert({ variant: 'danger', message })
+ }
+ }, [currentWallet, getWalletAllResponse, t])
return (
<>
{serviceInfo?.rescanning === true && (
@@ -216,51 +187,55 @@ export default function Wallets({ currentWallet, startWallet, stopWallet }: Wall
)}
{alert &&
}
- {isLoading ? (
+ {navigation.state === 'loading' ? (
{t('wallets.text_loading')}
) : (
- walletList?.map((walletFileName, index) => {
- const noneActive = !serviceInfo?.walletFileName
- const isActive = serviceInfo?.walletFileName === walletFileName
- const hasToken =
- currentWallet && currentWallet.token && currentWallet.walletFileName === serviceInfo?.walletFileName
+ sortWallets(getWalletAllResponse?.existingWallets?.wallets ?? [], serviceInfo?.walletFileName).map(
+ (walletFileName: Api.WalletFileName, index: number) => {
+ const noneActive = !serviceInfo?.walletFileName
+ const isActive = serviceInfo?.walletFileName === walletFileName
+ const hasToken =
+ currentWallet && currentWallet.token && currentWallet.walletFileName === serviceInfo?.walletFileName
- const showLockOptions = isActive && hasToken
- const showUnlockOptions =
- (!isUnlocking || unlockingWalletFileName === walletFileName) &&
- (noneActive || (isActive && !hasToken) || (!hasToken && !makerRunning && !coinjoinInProgress))
- return (
-
- )
- })
+ const showLockOptions = isActive && hasToken
+ const showUnlockOptions =
+ (!isUnlocking || unlockingWalletFileName === walletFileName) &&
+ (noneActive || (isActive && !hasToken) || (!hasToken && !makerRunning && !coinjoinInProgress))
+ return (
+
+ )
+ },
+ )
)}
0,
- disabled: isLoading || isUnlocking,
+ 'btn-lg': getWalletAllResponse?.existingWallets?.wallets?.length === 0,
+ 'btn-dark': getWalletAllResponse?.existingWallets?.wallets?.length === 0,
+ 'btn-outline-dark':
+ !getWalletAllResponse?.existingWallets?.wallets ||
+ getWalletAllResponse?.existingWallets?.wallets?.length > 0,
+ disabled: !getWalletAllResponse || isUnlocking,
})}
data-testid="new-wallet-btn"
>
@@ -273,8 +248,8 @@ export default function Wallets({ currentWallet, startWallet, stopWallet }: Wall
diff --git a/src/components/loaders/DataLoaders.ts b/src/components/loaders/DataLoaders.ts
new file mode 100644
index 000000000..f7a6008bd
--- /dev/null
+++ b/src/components/loaders/DataLoaders.ts
@@ -0,0 +1,15 @@
+import * as Api from '../../libs/JmWalletApi'
+import { t } from 'i18next'
+import { LoaderFunctionArgs } from 'react-router-dom'
+
+export const allWalletsLoader = async ({ request }: LoaderFunctionArgs) => {
+ try {
+ const res = await Api.getWalletAll(request)
+ const existingWallets = res.ok ? await res.json() : Api.Helper.throwError(res, t('wallets.error_loading_failed'))
+ return { existingWallets }
+ } catch (e: any) {
+ return { existingWalletsError: e.message }
+ }
+}
+
+export type AllWalletsLoaderResponse = Awaited>
diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json
index 5d046dded..648e72fdc 100644
--- a/src/i18n/locales/en/translation.json
+++ b/src/i18n/locales/en/translation.json
@@ -130,6 +130,7 @@
"label_wallet_name": "Wallet name",
"placeholder_wallet_name": "Your Wallet...",
"feedback_invalid_wallet_name": "Please choose a valid wallet name: Use only letters, numbers, underscores or hyphens.",
+ "feedback_wallet_name_already_exists": "Please choose another wallet name. This one is already in use.",
"label_password": "Password to unlock the wallet",
"placeholder_password": "Choose a secure password...",
"feedback_invalid_password": "Please set a password.",
diff --git a/src/index.tsx b/src/index.tsx
index 119ee5147..0933f6ea2 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,5 +1,4 @@
import { StrictMode } from 'react'
-import { createRoot } from 'react-dom/client'
import App from './components/App'
import { SettingsProvider } from './context/SettingsContext'
@@ -11,10 +10,11 @@ import { ServiceConfigProvider } from './context/ServiceConfigContext'
import 'bootstrap/dist/css/bootstrap.min.css'
import './index.css'
import './i18n/config'
+import ReactDOM from 'react-dom/client'
-const container = document.getElementById('root')
-const root = createRoot(container!)
-root.render(
+// const container = document.getElementById('root')
+// const root = createRoot(container!)
+ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(