diff --git a/packages/desktop-client/src/components/ServerContext.js b/packages/desktop-client/src/components/ServerContext.tsx
similarity index 65%
rename from packages/desktop-client/src/components/ServerContext.js
rename to packages/desktop-client/src/components/ServerContext.tsx
index ae0fb1b4355779c4ce265f2c1a4dade38e57ad57..86c0bf8d653da972f32a91ce9985f01441f9a67a 100644
--- a/packages/desktop-client/src/components/ServerContext.js
+++ b/packages/desktop-client/src/components/ServerContext.tsx
@@ -4,25 +4,39 @@ import React, {
   useCallback,
   useEffect,
   useContext,
+  type ReactNode,
 } from 'react';
 
 import { send } from 'loot-core/src/platform/client/fetch';
 
-const ServerContext = createContext({});
+type ServerContextValue = {
+  url: string | null;
+  version: string;
+  setURL: (
+    url: string,
+    opts?: { validate?: boolean },
+  ) => Promise<{ error?: string }>;
+};
+
+const ServerContext = createContext<ServerContextValue>({
+  url: null,
+  version: '',
+  setURL: () => Promise.reject(new Error('ServerContext not initialized')),
+});
 
 export const useServerURL = () => useContext(ServerContext).url;
 export const useServerVersion = () => useContext(ServerContext).version;
 export const useSetServerURL = () => useContext(ServerContext).setURL;
 
 async function getServerVersion() {
-  let { error, version } = await send('get-server-version');
-  if (error) {
-    return '';
+  let result = await send('get-server-version');
+  if ('version' in result) {
+    return result.version;
   }
-  return version;
+  return '';
 }
 
-export function ServerProvider({ children }) {
+export function ServerProvider({ children }: { children: ReactNode }) {
   let [serverURL, setServerURL] = useState('');
   let [version, setVersion] = useState('');
 
@@ -35,7 +49,7 @@ export function ServerProvider({ children }) {
   }, []);
 
   let setURL = useCallback(
-    async (url, opts = {}) => {
+    async (url: string, opts: { validate?: boolean } = {}) => {
       let { error } = await send('set-server-url', { ...opts, url });
       if (!error) {
         setServerURL(await send('get-server-url'));
diff --git a/packages/desktop-client/src/components/common/Input.tsx b/packages/desktop-client/src/components/common/Input.tsx
index 08e9fcf33a1987afc517311e002837cbd51b0689..2b09d772aa0aa4e79c786a4986f008a7eb21294f 100644
--- a/packages/desktop-client/src/components/common/Input.tsx
+++ b/packages/desktop-client/src/components/common/Input.tsx
@@ -23,14 +23,14 @@ type InputProps = HTMLPropsWithStyle<HTMLInputElement> & {
   focused?: boolean;
 };
 
-const Input = ({
+export default function Input({
   style,
   inputRef,
   onEnter,
   onUpdate,
   focused,
   ...nativeProps
-}: InputProps) => {
+}: InputProps) {
   let ref = useRef();
   useProperFocus(ref, focused);
 
@@ -68,6 +68,22 @@ const Input = ({
       }}
     />
   );
-};
+}
 
-export default Input;
+export function BigInput(props: InputProps) {
+  return (
+    <Input
+      {...props}
+      style={[
+        {
+          padding: 10,
+          fontSize: 15,
+          border: 'none',
+          ...styles.shadow,
+          ':focus': { border: 'none', ...styles.shadow },
+        },
+        props.style,
+      ]}
+    />
+  );
+}
diff --git a/packages/desktop-client/src/components/manager/ConfigServer.js b/packages/desktop-client/src/components/manager/ConfigServer.tsx
similarity index 89%
rename from packages/desktop-client/src/components/manager/ConfigServer.js
rename to packages/desktop-client/src/components/manager/ConfigServer.tsx
index 0c7d7f75975279e2b2c84aec98e8ed84af916d56..b12d4790ee64632b5c4ec651f9e9141dc182d9fe 100644
--- a/packages/desktop-client/src/components/manager/ConfigServer.js
+++ b/packages/desktop-client/src/components/manager/ConfigServer.tsx
@@ -1,26 +1,25 @@
 import React, { useState, useEffect } from 'react';
-import { useDispatch } from 'react-redux';
 import { useNavigate } from 'react-router-dom';
 
-import { createBudget } from 'loot-core/src/client/actions/budgets';
-import { signOut, loggedIn } from 'loot-core/src/client/actions/user';
 import {
   isNonProductionEnvironment,
   isElectron,
 } from 'loot-core/src/shared/environment';
 
+import { useActions } from '../../hooks/useActions';
 import { useSetThemeColor } from '../../hooks/useSetThemeColor';
 import { colors } from '../../style';
 import Button, { ButtonWithLoading } from '../common/Button';
+import { BigInput } from '../common/Input';
 import Text from '../common/Text';
 import View from '../common/View';
 import { useServerURL, useSetServerURL } from '../ServerContext';
 
-import { Title, Input } from './subscribe/common';
+import { Title } from './subscribe/common';
 
 export default function ConfigServer() {
   useSetThemeColor(colors.p5);
-  let dispatch = useDispatch();
+  let { createBudget, signOut, loggedIn } = useActions();
   let navigate = useNavigate();
   let [url, setUrl] = useState('');
   let currentUrl = useServerURL();
@@ -29,9 +28,9 @@ export default function ConfigServer() {
     setUrl(currentUrl);
   }, [currentUrl]);
   let [loading, setLoading] = useState(false);
-  let [error, setError] = useState(null);
+  let [error, setError] = useState<string | null>(null);
 
-  function getErrorMessage(error) {
+  function getErrorMessage(error: string) {
     switch (error) {
       case 'network-failure':
         return 'Server is not running at this URL. Make sure you have HTTPS set up properly.';
@@ -59,7 +58,7 @@ export default function ConfigServer() {
         setUrl('https://' + url);
         setError(error);
       } else {
-        await dispatch(signOut());
+        await signOut();
         navigate('/');
       }
       setLoading(false);
@@ -68,7 +67,7 @@ export default function ConfigServer() {
       setError(error);
     } else {
       setLoading(false);
-      await dispatch(signOut());
+      await signOut();
       navigate('/');
     }
   }
@@ -79,13 +78,13 @@ export default function ConfigServer() {
 
   async function onSkip() {
     await setServerUrl(null);
-    await dispatch(loggedIn());
+    await loggedIn();
     navigate('/');
   }
 
   async function onCreateTestFile() {
     await setServerUrl(null);
-    await dispatch(createBudget({ testMode: true }));
+    await createBudget({ testMode: true });
     window.__navigate('/');
   }
 
@@ -134,11 +133,11 @@ export default function ConfigServer() {
           onSubmit();
         }}
       >
-        <Input
+        <BigInput
           autoFocus={true}
-          placeholder={'https://example.com'}
+          placeholder="https://example.com"
           value={url || ''}
-          onChange={e => setUrl(e.target.value)}
+          onUpdate={setUrl}
           style={{ flex: 1, marginRight: 10 }}
         />
         <ButtonWithLoading
diff --git a/packages/desktop-client/src/components/manager/ServerURL.js b/packages/desktop-client/src/components/manager/ServerURL.tsx
similarity index 91%
rename from packages/desktop-client/src/components/manager/ServerURL.js
rename to packages/desktop-client/src/components/manager/ServerURL.tsx
index e90873a9cf0f6de5a60c8e33d0c1d6c908fe134d..3d62e31ab09f7b7e3723fd7232c020b36d0e63f3 100644
--- a/packages/desktop-client/src/components/manager/ServerURL.js
+++ b/packages/desktop-client/src/components/manager/ServerURL.tsx
@@ -30,7 +30,7 @@ export default function ServerURL() {
           <strong>No server configured</strong>
         )}
       </Text>
-      <AnchorLink bare to="/config-server" style={{ marginLeft: 15 }}>
+      <AnchorLink to="/config-server" style={{ marginLeft: 15 }}>
         Change
       </AnchorLink>
     </View>
diff --git a/packages/desktop-client/src/components/manager/subscribe/ConfirmPasswordForm.tsx b/packages/desktop-client/src/components/manager/subscribe/ConfirmPasswordForm.tsx
index 645c73e442bbd6df2f98d895cf0a264c9dae8f36..b0d5b3622ffd09742adcf51a8d0b94d2421eb527 100644
--- a/packages/desktop-client/src/components/manager/subscribe/ConfirmPasswordForm.tsx
+++ b/packages/desktop-client/src/components/manager/subscribe/ConfirmPasswordForm.tsx
@@ -1,10 +1,9 @@
 import React, { type ChangeEvent, useState } from 'react';
 
 import { ButtonWithLoading } from '../../common/Button';
+import { BigInput } from '../../common/Input';
 import View from '../../common/View';
 
-import { Input } from './common';
-
 export function ConfirmPasswordForm({ buttons, onSetPassword, onError }) {
   let [password1, setPassword1] = useState('');
   let [password2, setPassword2] = useState('');
@@ -40,7 +39,7 @@ export function ConfirmPasswordForm({ buttons, onSetPassword, onError }) {
       }}
       onSubmit={onSubmit}
     >
-      <Input
+      <BigInput
         autoFocus={true}
         placeholder="Password"
         type={showPassword ? 'text' : 'password'}
@@ -50,7 +49,7 @@ export function ConfirmPasswordForm({ buttons, onSetPassword, onError }) {
         }
         onEnter={onSubmit}
       />
-      <Input
+      <BigInput
         placeholder="Confirm password"
         type={showPassword ? 'text' : 'password'}
         value={password2}
diff --git a/packages/desktop-client/src/components/manager/subscribe/Login.tsx b/packages/desktop-client/src/components/manager/subscribe/Login.tsx
index 6303627e5376f4f5fdd6f5910ee3da8c11119eb7..1571bc06fdad279f59ebe4de10630b7c313592f2 100644
--- a/packages/desktop-client/src/components/manager/subscribe/Login.tsx
+++ b/packages/desktop-client/src/components/manager/subscribe/Login.tsx
@@ -7,10 +7,11 @@ import { send } from 'loot-core/src/platform/client/fetch';
 
 import { colors } from '../../../style';
 import Button, { ButtonWithLoading } from '../../common/Button';
+import { BigInput } from '../../common/Input';
 import Text from '../../common/Text';
 import View from '../../common/View';
 
-import { useBootstrapped, Title, Input } from './common';
+import { useBootstrapped, Title } from './common';
 
 export default function Login() {
   let dispatch = useDispatch();
@@ -88,7 +89,7 @@ export default function Login() {
         style={{ display: 'flex', flexDirection: 'row', marginTop: 30 }}
         onSubmit={onSubmit}
       >
-        <Input
+        <BigInput
           autoFocus={true}
           placeholder="Password"
           type="password"
diff --git a/packages/desktop-client/src/components/manager/subscribe/common.tsx b/packages/desktop-client/src/components/manager/subscribe/common.tsx
index 8a993c84edfde1002a2ee5a39352540edbd2d02a..562ec0e4edf303f61a3ba22bfbec7d2f13d2ff80 100644
--- a/packages/desktop-client/src/components/manager/subscribe/common.tsx
+++ b/packages/desktop-client/src/components/manager/subscribe/common.tsx
@@ -1,15 +1,9 @@
-import React, {
-  type ComponentProps,
-  forwardRef,
-  useEffect,
-  useState,
-} from 'react';
+import React, { useEffect, useState } from 'react';
 import { useNavigate, useLocation } from 'react-router-dom';
 
 import { send } from 'loot-core/src/platform/client/fetch';
 
-import { colors, styles } from '../../../style';
-import BaseInput from '../../common/Input';
+import { colors } from '../../../style';
 import { useSetServerURL } from '../../ServerContext';
 
 // There are two URLs that dance with each other: `/login` and
@@ -92,22 +86,3 @@ export function Title({ text }: TitleProps) {
     </h1>
   );
 }
-
-type InputProps = ComponentProps<typeof BaseInput>;
-export const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
-  return (
-    <BaseInput
-      {...props}
-      style={[
-        {
-          padding: 10,
-          fontSize: 15,
-          border: 'none',
-          ...styles.shadow,
-          ':focus': { border: 'none', ...styles.shadow },
-        },
-        props.style,
-      ]}
-    />
-  );
-});
diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts
index 8ce437c00631f071a71cd0e80a108f4989fee14e..e5875a77d689f5aa2e5d827a37badffb43fec590 100644
--- a/packages/loot-core/src/types/server-handlers.d.ts
+++ b/packages/loot-core/src/types/server-handlers.d.ts
@@ -282,9 +282,12 @@ export interface ServerHandlers {
 
   'get-server-version': () => Promise<{ error?: string } | { version: string }>;
 
-  'get-server-url': () => Promise<unknown>;
+  'get-server-url': () => Promise<string | null>;
 
-  'set-server-url': (arg: { url; validate }) => Promise<unknown>;
+  'set-server-url': (arg: {
+    url: string;
+    validate?: boolean;
+  }) => Promise<{ error?: string }>;
 
   sync: () => Promise<
     | { error: { message: string; reason: string; meta: unknown } }
diff --git a/upcoming-release-notes/1431.md b/upcoming-release-notes/1431.md
new file mode 100644
index 0000000000000000000000000000000000000000..82b214713661e8df8fe573687d57e525d7ab78e0
--- /dev/null
+++ b/upcoming-release-notes/1431.md
@@ -0,0 +1,6 @@
+---
+category: Maintenance
+authors: [j-f1]
+---
+
+Move big input component into Input.js, port some of the manager app to TS