From be76476dbd97f34f3927bb329f864e07d47f4754 Mon Sep 17 00:00:00 2001 From: Zeus <32263615+ZeusJunior@users.noreply.github.com> Date: Fri, 14 Nov 2025 23:47:05 +0100 Subject: [PATCH 1/8] feat: make some page components reusable (Error msg, primary/secondary button, header) --- .../AccountSelector/AccountList.tsx | 7 +- .../AccountSelector/AccountSelector.tsx | 14 ++- .../AccountSelector/Forms/ImportSDA.tsx | 43 ++++------ .../Forms/NewAuthenticator.tsx | 86 +++++++------------ .../AccountSelector/ImportOptions.tsx | 38 ++++---- renderer/components/ErrorMessage.tsx | 7 ++ renderer/components/Form/PrimaryButton.tsx | 44 ++++++++++ renderer/components/Form/SecondaryButton.tsx | 26 ++++++ .../components/Icons/SmallLoadingSpinner.tsx | 8 ++ renderer/components/Layout/PageContainer.tsx | 7 ++ renderer/components/Layout/PageHeader.tsx | 10 +++ renderer/components/PasswordAuth.tsx | 32 ++----- renderer/components/Sidebar.tsx | 35 +++----- renderer/pages/confirmations.tsx | 23 ++--- renderer/pages/debug.tsx | 5 +- renderer/pages/index.tsx | 21 +++-- 16 files changed, 215 insertions(+), 191 deletions(-) create mode 100644 renderer/components/ErrorMessage.tsx create mode 100644 renderer/components/Form/PrimaryButton.tsx create mode 100644 renderer/components/Form/SecondaryButton.tsx create mode 100644 renderer/components/Icons/SmallLoadingSpinner.tsx create mode 100644 renderer/components/Layout/PageContainer.tsx create mode 100644 renderer/components/Layout/PageHeader.tsx diff --git a/renderer/components/AccountSelector/AccountList.tsx b/renderer/components/AccountSelector/AccountList.tsx index 9863d62..76939a8 100644 --- a/renderer/components/AccountSelector/AccountList.tsx +++ b/renderer/components/AccountSelector/AccountList.tsx @@ -2,6 +2,7 @@ import Image from 'next/image'; import { useEffect, useState } from 'react'; import { useAccount } from '../../context/AccountContext'; import ReloadIcon from '../Icons/Reload'; +import { ErrorMessage } from '../ErrorMessage'; export default function AccountList({ onSelect }: { onSelect: (accountId: string) => void }) { const { accounts, isLoading, loadAccounts } = useAccount(); @@ -74,11 +75,7 @@ export default function AccountList({ onSelect }: { onSelect: (accountId: string )} - {error && ( -
- {error} -
- )} + {error && ()} {/* Accounts List */}
diff --git a/renderer/components/AccountSelector/AccountSelector.tsx b/renderer/components/AccountSelector/AccountSelector.tsx index 9437765..7b0bd99 100644 --- a/renderer/components/AccountSelector/AccountSelector.tsx +++ b/renderer/components/AccountSelector/AccountSelector.tsx @@ -5,6 +5,8 @@ import AccountList from './AccountList'; import NewAuthenticator from './Forms/NewAuthenticator'; import ImportSDA from './Forms/ImportSDA'; import ImportOptions from './ImportOptions'; +import { PageContainer } from '../Layout/PageContainer'; +import { ErrorMessage } from '../ErrorMessage'; interface AccountSelectorProps { onAccountSelected?: () => void; @@ -52,9 +54,9 @@ export default function AccountSelector({ onAccountSelected = () => { } }: Accou return ( <> - {isFirstAccount ? 'Add your first account - Thunder' : 'Select Account - Thunder'} + {isFirstAccount ? 'Add your first account' : 'Select account'} - Thunder -
+

@@ -67,11 +69,7 @@ export default function AccountSelector({ onAccountSelected = () => { } }: Accou

- {error && ( -
- {error} -
- )} + {error && ()} {!addAccountMode ? (
@@ -97,7 +95,7 @@ export default function AccountSelector({ onAccountSelected = () => { } }: Accou
)}
-
+ ); } diff --git a/renderer/components/AccountSelector/Forms/ImportSDA.tsx b/renderer/components/AccountSelector/Forms/ImportSDA.tsx index 857965d..c0a1582 100644 --- a/renderer/components/AccountSelector/Forms/ImportSDA.tsx +++ b/renderer/components/AccountSelector/Forms/ImportSDA.tsx @@ -1,4 +1,7 @@ import { useState } from 'react'; +import { ErrorMessage } from '../../ErrorMessage'; +import SecondaryButton from '../../Form/SecondaryButton'; +import PrimaryButton from '../../Form/PrimaryButton'; interface NewAuthenticatorProps { onSuccess: (accountId: string) => void; @@ -57,11 +60,7 @@ export default function ImportSDA({ onSuccess, onCancel }: NewAuthenticatorProps

- {error && ( -
-

{error}

-
- )} + {error && ()}
@@ -69,37 +68,25 @@ export default function ImportSDA({ onSuccess, onCancel }: NewAuthenticatorProps Select maFile
- - {selectedFile && ( - - {selectedFile.split(/[\\/]/).pop()} - - )} + text={selectedFile ? selectedFile.split(/[\\/]/).pop() as string : 'Browse files'} + />
- - + text='Cancel' + />
diff --git a/renderer/components/AccountSelector/Forms/NewAuthenticator.tsx b/renderer/components/AccountSelector/Forms/NewAuthenticator.tsx index bd54b52..eb09d72 100644 --- a/renderer/components/AccountSelector/Forms/NewAuthenticator.tsx +++ b/renderer/components/AccountSelector/Forms/NewAuthenticator.tsx @@ -1,4 +1,7 @@ import { useState } from 'react'; +import { ErrorMessage } from '../../ErrorMessage'; +import SecondaryButton from '../../Form/SecondaryButton'; +import PrimaryButton from '../../Form/PrimaryButton'; interface NewAuthenticatorProps { onSuccess: (accountId: string) => void; @@ -47,11 +50,7 @@ export default function NewAuthenticator({ onSuccess, onCancel }: NewAuthenticat ) } - {error && ( -
- {error} -
- )} + {error && ()} {getStepContent()} @@ -167,24 +166,16 @@ function LoginStep({ onCancel, setError, setSteamId, setRecoveryCode, nextStep } )}
- - + text='Cancel' + />
); @@ -237,28 +228,21 @@ function FinalizeStep({ onCancel, setError, nextStep, steamId }: FinalizeStepPro

- Node:If you have a phone number linked to your account, then you'll be sent an SMS with an activation code. Otherwise, you'll receive the activation code by email. + Note: If you have a phone number linked to your account, then you'll be sent an SMS with an activation code. Otherwise, you'll receive the activation code by email.

- - + text='Cancel' + />
); @@ -296,13 +280,11 @@ function CheckRecoveryCodeStep({ setError, recoveryCode, nextStep }: CheckRecove

{recoveryCode}

- + text='I have saved the recovery code' + /> ); } @@ -325,14 +307,10 @@ function CheckRecoveryCodeStep({ setError, recoveryCode, nextStep }: CheckRecove /> -
- -
+ ); } @@ -348,13 +326,11 @@ function CongratulationsStep({ onContinue }: CongratulationsStepProps) {

A Steam Guard authenticator has been successfully added to your account!

- + text='Continue' + /> ); } diff --git a/renderer/components/AccountSelector/ImportOptions.tsx b/renderer/components/AccountSelector/ImportOptions.tsx index 4678b00..a199f46 100644 --- a/renderer/components/AccountSelector/ImportOptions.tsx +++ b/renderer/components/AccountSelector/ImportOptions.tsx @@ -1,3 +1,5 @@ +import PrimaryButton from '../Form/PrimaryButton'; +import SecondaryButton from '../Form/SecondaryButton'; import CloudDownloadIcon from '../Icons/CloudDownload'; import PlusIcon from '../Icons/Plus'; @@ -11,25 +13,21 @@ export default function ImportOptions({ onSelect, isFirstAccount }: ImportOption return (
- + icon={} + text="New Authenticator" + /> - + text='Import from SDA maFile' + icon={} + />
); @@ -40,22 +38,18 @@ export default function ImportOptions({ onSelect, isFirstAccount }: ImportOption

Add another account:

- - + text="Import from SDA" + />
diff --git a/renderer/components/ErrorMessage.tsx b/renderer/components/ErrorMessage.tsx new file mode 100644 index 0000000..4b5f1ed --- /dev/null +++ b/renderer/components/ErrorMessage.tsx @@ -0,0 +1,7 @@ +export const ErrorMessage = ({ message }: { message: string }) => { + return ( +
+ {message} +
+ ); +}; \ No newline at end of file diff --git a/renderer/components/Form/PrimaryButton.tsx b/renderer/components/Form/PrimaryButton.tsx new file mode 100644 index 0000000..e246dd6 --- /dev/null +++ b/renderer/components/Form/PrimaryButton.tsx @@ -0,0 +1,44 @@ +import SmallLoadingSpinner from '../Icons/SmallLoadingSpinner'; + +interface ButtonProps { + onClick?: () => void; + text: string; + loadingText?: string; + icon?: React.ReactNode; + disabled?: boolean; + isLoading?: boolean; + type?: 'submit' | 'button'; +} + +export default function PrimaryButton({ + onClick, + text, + loadingText, + icon, + disabled, + isLoading, + type = 'submit', +}: ButtonProps) { + return ( + + ); +} \ No newline at end of file diff --git a/renderer/components/Form/SecondaryButton.tsx b/renderer/components/Form/SecondaryButton.tsx new file mode 100644 index 0000000..00a66b8 --- /dev/null +++ b/renderer/components/Form/SecondaryButton.tsx @@ -0,0 +1,26 @@ +interface ButtonProps { + onClick?: () => void; + text: string; + icon?: React.ReactNode; +} + +export default function SecondaryButton({ + onClick, + text, + icon, +}: ButtonProps) { + return ( + + ); +} \ No newline at end of file diff --git a/renderer/components/Icons/SmallLoadingSpinner.tsx b/renderer/components/Icons/SmallLoadingSpinner.tsx new file mode 100644 index 0000000..1661a07 --- /dev/null +++ b/renderer/components/Icons/SmallLoadingSpinner.tsx @@ -0,0 +1,8 @@ +export default function SmallLoadingSpinner() { + return ( + + + + + ); +} \ No newline at end of file diff --git a/renderer/components/Layout/PageContainer.tsx b/renderer/components/Layout/PageContainer.tsx new file mode 100644 index 0000000..eb1b157 --- /dev/null +++ b/renderer/components/Layout/PageContainer.tsx @@ -0,0 +1,7 @@ +export const PageContainer = ({ children }: { children: React.ReactNode }) => { + return ( +
+ {children} +
+ ); +}; \ No newline at end of file diff --git a/renderer/components/Layout/PageHeader.tsx b/renderer/components/Layout/PageHeader.tsx new file mode 100644 index 0000000..f73ea29 --- /dev/null +++ b/renderer/components/Layout/PageHeader.tsx @@ -0,0 +1,10 @@ +export const PageHeader = ({ title, children }: { title: string; children?: React.ReactNode }) => { + return ( +
+

+ {title} +

+ {children && children} +
+ ); +}; \ No newline at end of file diff --git a/renderer/components/PasswordAuth.tsx b/renderer/components/PasswordAuth.tsx index 12bcd95..b207492 100644 --- a/renderer/components/PasswordAuth.tsx +++ b/renderer/components/PasswordAuth.tsx @@ -1,5 +1,7 @@ import Head from 'next/head'; import React, { useState } from 'react'; +import { ErrorMessage } from './ErrorMessage'; +import PrimaryButton from './Form/PrimaryButton'; interface PasswordAuthProps { isFirstTime: boolean @@ -106,31 +108,13 @@ export default function PasswordAuth({ isFirstTime, onAuthenticated }: PasswordA )} - {error && ( -
- {error} -
- )} + {error && ()} -
- -
+ diff --git a/renderer/components/Sidebar.tsx b/renderer/components/Sidebar.tsx index e669979..521fcfe 100644 --- a/renderer/components/Sidebar.tsx +++ b/renderer/components/Sidebar.tsx @@ -8,6 +8,8 @@ import ArrowLRIcon from './Icons/ArrowsLR'; import Popup from './Popup/Popup'; import ExternalIcon from './Icons/External'; import DocumentCheckIcon from './Icons/DocumentCheck'; +import { ErrorMessage } from './ErrorMessage'; +import PrimaryButton from './Form/PrimaryButton'; export default function Sidebar() { const { currentAccount } = useAccount(); @@ -167,11 +169,8 @@ export default function Sidebar() { close={() => setIsPopupOpen(false)} > - {popupError && ( -
- {popupError} -
- )} + {popupError && ()} +
diff --git a/renderer/pages/confirmations.tsx b/renderer/pages/confirmations.tsx index 0792de4..e39f38d 100644 --- a/renderer/pages/confirmations.tsx +++ b/renderer/pages/confirmations.tsx @@ -3,6 +3,9 @@ import Head from 'next/head'; import Image from 'next/image'; import { Confirmation } from '../../main/types'; import Popup from '../components/Popup/Popup'; +import { PageContainer } from '../components/Layout/PageContainer'; +import { PageHeader } from '../components/Layout/PageHeader'; +import { ErrorMessage } from '../components/ErrorMessage'; interface LoadingConfirmations extends Confirmation { isLoading?: boolean; @@ -77,11 +80,8 @@ export default function ConfirmationsPage() { Confirmations - Thunder -
-
-

- Confirmations -

+ + {!isLoading && confirmations.length > 0 && ( )} -
+ - {/* TODO: Make error alert reusable */} - {error && ( -
- {error} -
- )} + {error && ()} {isLoading && (
@@ -192,14 +187,14 @@ export default function ConfirmationsPage() {
)} -
+ ); } diff --git a/renderer/pages/debug.tsx b/renderer/pages/debug.tsx index 7ab0dca..ff78620 100644 --- a/renderer/pages/debug.tsx +++ b/renderer/pages/debug.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import Head from 'next/head'; import { DebugInfo } from '../../main/types'; +import { PageContainer } from '../components/Layout/PageContainer'; export default function DebugPage() { const [debugInfo, setDebugInfo] = useState(null); @@ -18,11 +19,11 @@ export default function DebugPage() { Debug! - Thunder -
+
           {JSON.stringify(debugInfo, null, 2)}
         
-
+ ); } diff --git a/renderer/pages/index.tsx b/renderer/pages/index.tsx index f60df06..0de8a9f 100644 --- a/renderer/pages/index.tsx +++ b/renderer/pages/index.tsx @@ -3,6 +3,9 @@ import Head from 'next/head'; import { useAccount } from '../context/AccountContext'; import ClockIcon from '../components/Icons/Clock'; import CopyIcon from '../components/Icons/Copy'; +import { PageContainer } from '../components/Layout/PageContainer'; +import { PageHeader } from '../components/Layout/PageHeader'; +import PrimaryButton from '../components/Form/PrimaryButton'; export default function HomePage() { const { currentAccount, isLoading, seconds, authCode } = useAccount(); @@ -28,10 +31,8 @@ export default function HomePage() { Home - Thunder -
-

- Welcome to Thunder Authenticator - {currentAccount.personaName} -

+ +
@@ -62,13 +63,11 @@ export default function HomePage() {
- + icon={} + text="Copy code" + /> ) : (
@@ -79,7 +78,7 @@ export default function HomePage() {
- + ); } From c0ff155070e0db270ac199301d80817e2c85d915 Mon Sep 17 00:00:00 2001 From: Zeus <32263615+ZeusJunior@users.noreply.github.com> Date: Sat, 15 Nov 2025 01:25:45 +0100 Subject: [PATCH 2/8] feat: add basic settings page with app version and account secrets export --- main/background.ts | 12 +++ main/helpers/steam.ts | 3 +- main/preload.ts | 3 + main/types.ts | 1 + renderer/components/Icons/Settings.tsx | 8 ++ renderer/components/PasswordAuth.tsx | 1 + renderer/components/Sidebar.tsx | 8 ++ renderer/next.config.js | 5 ++ renderer/pages/settings.tsx | 111 +++++++++++++++++++++++++ 9 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 renderer/components/Icons/Settings.tsx create mode 100644 renderer/pages/settings.tsx diff --git a/main/background.ts b/main/background.ts index b4c1d20..58deb60 100644 --- a/main/background.ts +++ b/main/background.ts @@ -263,6 +263,18 @@ handleIpc('get-auth-code', async () => { return getAuthCode(account.sharedSecret); }); +handleIpc('export-account-secrets', async () => { + const account = getCurrentAccount(false); + if (!account) { + throw new Error('No current account set'); + } + + return { + sharedSecret: account.sharedSecret, + identitySecret: account.identitySecret, + }; +}); + handleIpc('show-mafile-dialog', async () => { const result = await dialog.showOpenDialog({ title: 'Select maFile', diff --git a/main/helpers/steam.ts b/main/helpers/steam.ts index 39392d8..efe552b 100644 --- a/main/helpers/steam.ts +++ b/main/helpers/steam.ts @@ -25,8 +25,7 @@ export function loginAgain(details: SteamUser.LogOnDetailsNamePass | SteamUser.L }; user.on('error', (err) => { - // TODO: Figure out specific EResult for invalid/expired refresh token? - // TODO: Handle this error better in the UI + // TODO: Handle any errors here better in the UI console.error('Error re-authenticating:', err); return reject(new Error(err.message)); }); diff --git a/main/preload.ts b/main/preload.ts index cbcef22..2d6b3ef 100644 --- a/main/preload.ts +++ b/main/preload.ts @@ -43,6 +43,9 @@ const handler = { getAuthCode: () => { return invoke('get-auth-code'); }, + exportTradingBotSecrets: () => { + return invoke('export-account-secrets'); + }, showMaFileDialog: () => { return invoke('show-mafile-dialog'); diff --git a/main/types.ts b/main/types.ts index 434666d..62c5d1f 100644 --- a/main/types.ts +++ b/main/types.ts @@ -97,6 +97,7 @@ export interface IpcHandlers { 'finalize-authenticator': (steamId: string, activationCode: string) => Promise; 'login-again': (password: string) => Promise; 'get-auth-code': () => Promise; + 'export-account-secrets': () => Promise<{ identitySecret: string; sharedSecret: string }>; 'show-mafile-dialog': () => Promise; 'import-mafile': (filePath: string) => Promise; 'get-confirmations': () => Promise; diff --git a/renderer/components/Icons/Settings.tsx b/renderer/components/Icons/Settings.tsx new file mode 100644 index 0000000..d0090fd --- /dev/null +++ b/renderer/components/Icons/Settings.tsx @@ -0,0 +1,8 @@ +export default function SettingsIcon({ className }: { className?: string }) { + return ( + + + + + ); +} diff --git a/renderer/components/PasswordAuth.tsx b/renderer/components/PasswordAuth.tsx index b207492..16f6793 100644 --- a/renderer/components/PasswordAuth.tsx +++ b/renderer/components/PasswordAuth.tsx @@ -114,6 +114,7 @@ export default function PasswordAuth({ isFirstTime, onAuthenticated }: PasswordA type="submit" isLoading={isLoading} text={isFirstTime ? 'Create password' : 'Unlock'} + loadingText={isFirstTime ? 'Setting password...' : 'Unlocking...'} /> diff --git a/renderer/components/Sidebar.tsx b/renderer/components/Sidebar.tsx index 521fcfe..62ccc22 100644 --- a/renderer/components/Sidebar.tsx +++ b/renderer/components/Sidebar.tsx @@ -8,6 +8,7 @@ import ArrowLRIcon from './Icons/ArrowsLR'; import Popup from './Popup/Popup'; import ExternalIcon from './Icons/External'; import DocumentCheckIcon from './Icons/DocumentCheck'; +import SettingsIcon from './Icons/Settings'; import { ErrorMessage } from './ErrorMessage'; import PrimaryButton from './Form/PrimaryButton'; @@ -151,6 +152,13 @@ export default function Sidebar() { {/* Bottom Icons */}
+ + +