Ferni Finance - WordPad

Case study 02 / Product design / 2026

Ferni Finance

What's left after CPP and EI? An app for your first real paycheque.

Personal finance for people in Canada starting their first career job, with the jargon explained where it appears.

16lessons, plus a 40-term glossary

Role, status and stack
Role
Solo, product design and development
Status
Live
Made with
TypeScript / Next.js / Supabase / PostgreSQL

The problem

Your first real paycheque arrives with a lot of unexplained abbreviations.

What's left after CPP and EI? TFSA or RRSP first? Generic budgeting apps don't answer in Canadian terms, and don't teach you anything along the way. Ferni puts a salary calculator, a budget tracker, savings goals, a first-job checklist and short lessons on one account.

Ferni's landing page: the headline 'Your first salary, handled' next to a preview of the dashboard with income, expenses and savings rate.
The landing page, with sample figures in its dashboard preview.
  • Ferni's landing page on a laptop and a phone.
  • Ferni's landing page in a browser window.
  • Ferni's landing page on a phone.

Screens captured from the live site, shown in device frames.

Concept and interaction design

The idea

Built around the first months of a first job: what's left after deductions, where it should go, and what the words on the pay stub mean. Everything sits on one account, so the budget can start from the salary you already entered.

Ferni's landing page on a phone.
Jargon, explained in place
CPP and EI on the salary breakdown, and FHSA on the first-job checklist, open a short definition right where they appear. Nobody should have to leave the page to find out what came off their paycheque.
Three inputs, one answer
The salary calculator asks for a salary, a province and how often you're paid, and answers per pay and per year.
An estimate says so
The result is labelled an estimate, and the label can't be dismissed, because that's what it is.

Product design

I designed Ferni's look and built it. Every color is a design token in one file, so changing a value updates the whole app. See it with the other designs.

Color
A dark palette, lavender primary, rose accent
Type
Inter with DM Serif Display
Identity
SVG logo and wordmark, branded social image

How it fits together

Ferni Finance: Next.js front end, then Supabase auth, then Postgres with RLS, then Pure tax function. Choose a step for details. All steps are also listed under the diagram.

Next.js front end. App Router pages, with a proxy that checks the session on each request and sends signed-out visitors to login.

Read all 4 steps at once
  1. Next.js front end. App Router pages, with a proxy that checks the session on each request and sends signed-out visitors to login.
  2. Supabase auth. Cookie-based sessions, so server-rendered pages know who's asking.
  3. Postgres with RLS. Nine tables, each with Row Level Security on and policies scoped to the signed-in user.
  4. Pure tax function. calculateTax() takes a salary, province and pay frequency and returns a breakdown. It touches no database, so it's easy to test.
Why Row Level Security matters here
This app holds people's income and spending. With RLS, the database refuses to return a row unless its user_id matches the signed-in user. A bug in my code can't hand one person's budget to another, because the rule doesn't live in my code.
The tax logic
Federal and provincial brackets for all 13 provinces and territories, basic personal amounts, CPP and EI with their caps, and Quebec's reduced EI rate. The output is labelled an estimate, with no dismiss button, because that's what it is.
const cpp = Math.min(
  Math.max(0, (annualSalary - CPP_BASIC_EXEMPTION) * CPP_RATE),
  CPP_MAX_CONTRIBUTION
);

const eiRate = province === "QC" ? EI_RATE_QC : EI_RATE;
const eiMax = province === "QC" ? EI_MAX_PREMIUM_QC : EI_MAX_PREMIUM;
const ei = Math.min(annualSalary * eiRate, eiMax);
From lib/utils/tax.ts: CPP and EI, with their caps and the Quebec exception

Results

provinces and territories in the salary calculator
13
tables with Row Level Security
9
unit tests passing (tax and formatting)
60
Test case: Ontario, $60,000, paid biweekly
LineExpected
Federal tax$6,644.25
Ontario tax$2,430.82
CPP$3,361.75
EI$996.00
Net annual$46,567.18
Net per pay$1,791.05
Expected values from lib/utils/__tests__/tax.test.ts, worked out from the constants in the code. They check the arithmetic and the caps, not whether the constants themselves are right.

What I learned

What I learned

Writing the schema and security rules first made everything after it simpler. The six modules share one account, so a budget can see a salary without a pile of extra plumbing.

What I'd do differently

Keep the tax tables as dated data with a source for each number, and test against published examples instead of values worked out from my own constants. Tests can only catch what they're pointed at.

Built with an AI coding assistant. I decided the data model and the security rules, and checked the results.

Properties

Type
Web app
Role
Solo, product design and development
Year
2026
Status
Live
Case study, about 2 min read2026