Show HN: TopHat Finance – free, open, and offline
athenodoros.github.io
athenodoros.github.io
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!
Cheers.
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.
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.
I went to the site on my 27" iMac and got the 'Desktop Required' message.
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.3 Safari/605.1.15
Resolution per Panopticlick [0] is 2560x1440x24.For what it's worth, I'm also running Firefox on MacOS, so I'd expect that to work given the right window size.
This is a ridiculous "feature" and the fact that you are defending something like this is a major turn off on ever using this product. What other hill would you be "willing to die on" for some seemingly miniscule yet exceptionally annoying feature.
> Nobody here cares about the product specifically,
Citation needed.
> but it’s shooting itself in the foot and saying it’s a feature; that doesn’t sit well with many.
That's their problem, no one else's.
That's a pattern that most people would agree they don't like and my comment is meant to be a criticism of that.
> That's their problem, no one else's.
It was posted in a public forum for discussion, is it really wrong to critique or praise the product in this setting?
It's probably true that a summary of features or something would be more helpful for those coming across it anew. In this case though, this is mostly for my own benefit, and it's explicitly out of scope to make money off this: I'd prefer to spend time on additional features for users (ie. me!), rather than implementing something like a product showcase or proper mobile view. Hopefully it's useful/interesting enough when it can be viewed properly, that it's worth coming back to.
For other mobile users, you can use Firefox+ublock to zap the overlay(Addon>ublock>element zapper> need to zap the entire screen a few times(not just the dialog) until icons are individually zap-able >swipe right twice to exit zap mode). It is designed for desktop and there is some overlap in charts, but looks usable, on first look nonetheless.
Pop up warning: This will be hard to use on your screen size, continue?
If they click Yes then scale width to screen width (so no scrollbars)
If they click No take them to a description page with screenshots.
min-width: 1199px; overflow-x: scroll;
on the <body> element, and change the 100vw on the 'css-obv7rs' element to 100%? (And you may need some more tinkering, but you get the general idea.)
You can still put a mobile popup there ("this has been designed for a desktop"), but mobile users can pan around if they wish - degraded experience, but can use if they're desperate.
I've just updated it, everyone should be getting an updated version now (clearly there's interest!). Thanks for the thought!
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...
- 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?
There's definitely been some prior requests for some additional functionality around `createEntityAdapter`. We tried to keep the original implementation fairly scoped. (The current code is actually a port of the `@ngrx/entity` package.) There were a couple suggestions around having the ability to specify additional sorting indices of some kind. I briefly tried to play with implementing that idea [0], but wasn't sure it was the right direction and haven't gotten back to it. We don't do anything about logic for creating new items or auto-generating IDs atm. But, we are definitely interested in any specific suggestions for API improvements or additional use cases that it would be good to cover.
If this "page state" is mostly coming from the server and is primarily being used as cached data, it might actually be a good candidate for our RTK Query data fetching and caching API [1] [2]. If on the other hand you need those for a lot of other calculations in the slices, then RTKQ probably isn't the right choice.
`createAsyncThunk` specifically implements the standard "dispatch actions based on an async request lifecycle" pattern that's always been taught for Redux usage [3]. That way, you only have to provide a "payload creator" function that makes the async request and returns the data, and it dispatches the actions for you. Typically you then dispatch the thunk itself from a component, like `dispatch(fetchMyData())`.
FWIW I and the other Redux maintainers generally hang out in the `#redux` channel in the Reactiflux Discord ( https://www.reactiflux.com ), and we're always happy to chat and answer questions. Please drop by when you get a chance - I'd love to discuss some of these topics in more detail!
[0] https://github.com/reduxjs/redux-toolkit/pull/948
[1] https://redux-toolkit.js.org/rtk-query/overview
[2] https://redux.js.org/tutorials/essentials/part-7-rtk-query-b...
[3] https://redux.js.org/tutorials/fundamentals/part-7-standard-...
[4] https://redux.js.org/tutorials/fundamentals/part-8-modern-re...
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.
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.
This would be a good way to for users to incrementally take more control over their financial infrastructure.
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
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.
> 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.
> "The *demo* API key is for demo purposes only. Please claim your free API key at (https://www.alphavantage.co/support/#api-key) to explore our full API offerings. It takes fewer than 20 seconds."
I thought I'd added a description to the error you get if you're still using the default "demo" key - clearly not, I'll add that it.
We in the plain text accounting community should try to use it. (What's the license ?)
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!
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.
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.