Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: TopHat Finance – free, open, and offline (athenodoros.github.io)
104 points by Athenodoros on Feb 27, 2022 | hide | past | favorite | 43 comments



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!


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/


"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.


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.


> 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.

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.

[0] https://panopticlick.eff.org/


It's looking at the width, so that some fixed-width elements don't run into each other and wreck the display - if you expand the window a little it should work as expected.

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 looks lovely, well done! Regarding the cut off, why not use:

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.


That's a good idea - hard to believe that I didn't think of "min-width" considering the amount of other CSS that had to go into this, I wish I'd come up with it myself 12 hours ago...

I've just updated it, everyone should be getting an updated version now (clearly there's interest!). Thanks for the thought!


I suggest:

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.


I really think the design should be mobile first.

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.


Whether or not I can zoom out enough on my phone depends on your website. If you set the mobile screen width in your meta tag to 1280 or something, instead of ‘device-width’, then I’ll be able to use the site fine, as long as I’m willing to zoom and pan a bit.


Fair enough. Maybe the mobile version could just showcase the features and such of the « Desktop » version.


It's not just an annoyance, it makes your site literally unusable on a mobile device. What if I urgently need to access some details of my account and don't have laptop access. If I check "load desktop site" then load the site and let me deal with the broken UI.

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.


It's open source, not a product. You're coming off as entitled, and he can die on whatever hill he wants to die on. You can fork it, and even submit a PR that would do something like click a button to continue anyways (on a smaller resolution).


Maybe rude but not entitled. Nobody here cares about the product specifically, but it’s shooting itself in the foot and saying it’s a feature; that doesn’t sit well with many.

https://xkcd.com/386/


Incredibly entitled actually.

> 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.


They don't mean literally nobody cares about the product, just that this conversation isn't necessarily related directly to the product but more about design in general. My original point is just that it's a design that locks people into using the app with a very specific medium and it locks people out of accessing it on another medium with no details about why.

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?


Nah you’re wrong on all counts. If a product makes bad decisions and then the author posts it on HN what do they expect exactly?


I appreciate rapind's point so I won't comment along those lines, but I think it's worth explicitly saying that the UI doesn't work within a certain screen width: it would display nonsensically, and I'd rather display an explanation to someone new to it (and the link to the GitHub ReadMe, which hopefully helps a little) rather than complete garbage. I definitely don't consider this a "feature" - I think it's just the least bad option, given the lack of a proper mobile view. It's also the case that the idea of "urgent access" doesn't work so well without data syncing - I generally wouldn't expect people to copy the export file to their mobile manually, in which case this won't actually have the data from another device.

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.


I can appreciate that solution. Admittedly I didn't know anything about the app since I don't have access to any device to view it.


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...


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?


Thanks for the feedback, and glad to hear it's working well overall!

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...


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.


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?


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.


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.


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


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.


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.


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?


I believe it first happened after cleaning the demo to start from zero. But the apps is quite buggy in other ways. For instance, trying to add the GBP currency fails silently, but it's apparently due to using a free API?

> "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."


That's right - without a backend server, I couldn't think of a great way to manage a private API key. That means that users have to sign up for their own keys (instructions on the "Currency Sync" page under "Settings") if they want to use currency sync.

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.


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 ?)


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!


GPLv3+ gives you the most options. You can always relax it later if needed (easy to do as long as you are the sole copyright holder).


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"


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.


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


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.


You keep pseudo apologizing for this being "another" personal finance tracker. I'm actually not sure how many there are of note. I'm aware of YNAB and Toshl. Are there more that do similar things?


I use http://homebank.free.fr/ but I also don't need too many features.





Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: