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