top | item 30484235

Show HN: TopHat Finance – free, open, and offline

104 points| Athenodoros | 4 years ago |athenodoros.github.io

43 comments

order

Athenodoros|4 years ago

Hey HN - long time lurker etc.

First off - I know, another personal finance app. However, I found none of the existing options matched what I wanted for myself:

* Privacy-first - ideally a local app, and definitely no cloud storage of bank creds for automatic sync. I found this requires a polished statement upload flow, to save time on manual updates.

* Good multi-currency support - this is bizarrely rare in the space.

* Basic analytics and budgeting - ideally not as rigid as YNAB and friends, but at least something lightweight to tell me where my money is going and how that's changing.

So over a few months during Covid, I put together TopHat for my own use: it's a SPA on GitHub Pages with no backend, where all data is stored in browser storage (plus a Dropbox sync option, which I wanted as a backup). It's a pretty standard stack (React, Redux, TS), and the code is all available on GitHub (https://github.com/Athenodoros/TopHat) - don't judge too harshly, my day job isn't software engineering! That said, I've found it very rewarding to use something which I enjoy and which I've built - not to mention finally "finishing" a project...

TopHat can load itself with some demo data, including an example bank statement for upload (check the notification on the top right). Hopefully this is interesting for folks - happy to answer any questions!

billylo|4 years ago

Nice work! I made a similar tool, but focused on credit card spendings, runs on mobile, everything local, (with import from Canadian banks, also done locally using WebView so passwords never go elsewhere. I don't have enough time to continue to maintain it, but hope it can give you some other ideas to consider.

Cheers.

https://evergreen-labs.com/spendsimple/

andrew_|4 years ago

"Desktop Required"

I hate to doodie in the cereal, but... this is horrible UX. Warn the user, sure. But to block the user? I'm an Android user too; on tablets and phones. And both devices have landscape mode and a handy little checkbox in Brave/Chrome/etc that says "Desktop site."

You had my interest. But you lost it immediately after visiting.

Athenodoros|4 years ago

I definitely get the annoyance. The problem is, a mobile version of this is a very different thing, to the point that it's essentially a completely new app. Displaying a broken interface just to display something isn't helpful, even if I realise it makes it harder on the Show HN audience - I generally check HN on my phone too!

For what it's worth, I'm not looking at user-agents or anything: it's cutting off at the point where the display starts looking awful. If you zoom out enough (plausible on a tablet, less so on a phone), then it will display as intended.

acemarke|4 years ago

Looks very nice!

I see you're using Redux Toolkit and TypeScript, including some fairly heavy use of `createEntityAdapter`. Any feedback on using RTK? We're always interested in hearing how RTK is working for our users.

A couple quick suggestions on the code itself:

- I see a lot of custom hooks like `useAccountsPageState` [0], except that they're completely duplicating the `useSelector` signature. I think the right answer here is to start by defining the "pre-typed" `useAppSelector` hook we show in our TS usage docs [1]. Then, make sure your selectors have the correct `RootState` type. Finally, you can rewrite the hook as `const useAccountsPageState = () => useAppSelector(selectPageState)`.

- It looks like you're importing `TopHatDispatch` into some other files. That has the risk of causing circular import problems, since it's importing from the store itself. You may want to rework functions like `updateSyncedCurrencies` [2] to be a thunk. (In fact, looking at the code, this looks like a very good candidate for RTK's `createAsyncThunk` API.) Also, it looks like this is going to end up dispatching a separate action for every item. It would probably be more efficient to fetch all the items first, then dispatch _one_ action with the combined fetched results.

[0] https://github.com/Athenodoros/TopHat/blob/a916386edf/src/st...

[1] https://redux.js.org/tutorials/typescript-quick-start#define...

[2] https://github.com/Athenodoros/TopHat/blob/a916386edf/src/st...

Athenodoros|4 years ago

I definitely have a lot of opinions about this :D. The main one is that RTK was fantastic (as are the docs!), and createEntityAdapter was definitely better than the equivalent that I wrote before finding it... A few more specific thoughts though:

- createEntityAdapter was great, but one idea for an addition: I dislike having numbers as IDs (Will floating point precision bite me? Will I accidentally treat them as actual numbers? Do I need to cast them back for lookups or equality checks?), but I didn't get around to moving to strings, and preserving the sorting and ID generation with that in mind. Maybe RTK could bundle some utilities for managing this, like sorting functions and/or ascending ID generation to save others from my fate? I'll definitely start with these next time.

- I really liked the Slice API in RTK, but I felt like I wanted to run nested slices quite a lot - breaking up serialisable state (ie. user data) and page state, but then breaking down further into individual data types or pages respectively, so that pages could "own" their own state (and maybe get as far as something like a managed Mixin for a page state, like the recurring transaction table). I spent some time writing a way to compose Slices, but I found that it made things probably more complicated than it needed to be - even if you end up with a load of useAccountsPageState-style duplication which is maintained manually. (I'll admit this was a while ago, so my memory of why I decided against this is a little rusty...).

- I hadn't seen useAppDispatch and useAppSelector - I'll definitely swap over my copy of them in https://github.com/Athenodoros/TopHat/blob/main/src/state/sh... ...

- I definitely got that the expected pattern is createAsyncThunk (or useDispatch more broadly) rather than my separate functions using TopHatDispatch. I spent some time tooling about with migrating over, but I felt like it was an additional abstraction layer that I didn't really need. I eventually went with a fairly strict flow of "Types -> Storage Logic -> Store Definition -> Actions -> Components" which dealt with the circular imports. I'd be interested in other thoughts behind the best practice though - maybe they're more obvious with other people, or a larger project?

wnolens|4 years ago

Cool, styling is slick.

A good friend of mine has pretty much everything you built, but as an elaborate excel sheet (he's quite the wizard). I for one don't care to track individual transactions.

I'm building something similar (more along the lines of your forecast page), and also opting to do without a backend server. Love the flow of no login and everything private. I've opted to use uPlot as I'm not using React, but "starred" Victory for consideration someday as it looks nice.

bradly|4 years ago

I’m not super familiar with offline apps, but does this mean if I clear my browser or switch browsers or switch computers all my data my be re-entered?

Athenodoros|4 years ago

Yes, that's correct - it's the price of the data not being synced elsewhere.

That said, I was worried about that too, for myself: I've added in easy file import/export, and the option to sync to Dropbox (I'm using a separate account for TopHat syncing), so that people at least have options for backups.

infogulch|4 years ago

It would be awesome if it was able to sync via git in a way that is compatible with plain text accounting: https://plaintextaccounting.org/

This would be a good way to for users to incrementally take more control over their financial infrastructure.

alonchb|4 years ago

I also was in the same boat as you, I even paid for a year of YNAB. However, I had anxiety of giving full access to my bank account and importing it manually became a chore, soon I just stopped all together.

I support your decision of blocking phone access, even tho it was a surprise, maybe explaining why is better than label it as a technical limitation

Best of luck

idiocrat|4 years ago

Can you add an automatic consumption tax split?

For example if I book an expense of 11,000 jpy, I need to split this into two accounts:

- the expense account 10,000 jpy

- the consumption tax amount 1,000 jpy

Current tax rate is 10%, or 8% for selected items.

The actual tax amount must be able to adjust by +/- 1 jpy to match the receipt amount, because of inconsistent rounding/truncation methods.

Cynddl|4 years ago

It doesn't appear to be very stable. I keep running into the following error on Firefox and need to empty the memory and cache, then restart everything again:

> TopHat has hit an error, and has ended up in a bad state. You could go back to the home page, or check the developer tools for more information.

Athenodoros|4 years ago

That's interesting - I haven't seen errors in quite a while (on Firefox), and I've been using it myself for a few months. What are you doing just before it crashes?

simonmic|4 years ago

That is lovely, one of the nicest UIs/set of reports I've seen so far. Congrats!

We in the plain text accounting community should try to use it. (What's the license ?)

Athenodoros|4 years ago

Honestly, I haven't really got as far as a license for the code yet - I don't know much about them, and people seem to have strong opinions about the standard options for OSS.

I'd be interested in what folks recommend for something like this, and what the trade-offs are. Seems like MIT is the go-to in many cases?

EDIT: Done, on GPLv3!

regularperson25|4 years ago

Hey, normal user here. It's not clear how can I change the default currency from AU to USD? I see the option, but just to create the currency, everything I enter and all other forecast etc, says "AU"

Athenodoros|4 years ago

Oops - I guess that needs to be better signposted...

If you go to the currency page in the dialog (where you're creating USD), there's a "Default Currency" dropdown in the main section on the right. You might need to close out of any currency you're editing, but hopefully it's reasonably prominent once you do.

jeremywho|4 years ago

Does this talk to an API to get the bank data or are you manually entering it?

Athenodoros|4 years ago

Nope, no APIs - instead, I've focussed on a smooth workflow for statement uploads and options for manual transaction reporting.

I actually had a look around for APIs, and couldn't find anything that fitted with what I was trying to do: most services are specific to certain countries, and even with the various Open Banking initiatives around the world coverage is still limited and password storage upstream is often required (Plaid is obviously the standout here, although there are competitors and regional equivalents). That's expressly what I'm trying to avoid: ideally all of that could be managed locally in the browser, but until it can I'm not expecting to use it.