[{"data":1,"prerenderedAt":401},["ShallowReactive",2],{"/projects/flo":3},{"id":4,"title":5,"body":6,"description":358,"extension":384,"featured":385,"meta":386,"navigation":387,"path":388,"seo":389,"sortOrder":390,"stem":391,"tagline":392,"tags":393,"__hash__":400},"projects/projects/flo.md","Flo",{"type":7,"value":8,"toc":357},"minimark",[9,13,17,20,25,29,32,40,42,46,96,98,102,105,108,110,114,117,120,134,137,139,143,148,156,160,171,175,189,193,201,205,213,217,222,226,231,233,237,245,249,261,265,270,274,296,300,308,310,314,331,333,337,340,342,346],[10,11],"stack-chips",{":items":12},"[\"Next.js 15\", \"NestJS\", \"Fastify\", \"PostgreSQL\", \"Prisma 7\", \"Clerk\", \"Basiq API v3.0\", \"React Query\", \"Framer Motion\", \"Docker\", \"TypeScript\"]",[14,15],"media-carousel",{":items":16},"[{\"type\": \"image\", \"src\": \"/images/projects/flo/landing-page.png\", \"caption\": \"Marketing landing page - Open Banking positioning, security messaging, and onboarding entry points.\"}, {\"type\": \"image\", \"src\": \"/images/projects/flo/sign-up.png\", \"caption\": \"Account creation flow - Google/Apple OAuth alongside email sign-up, with a live dashboard preview panel.\"}, {\"type\": \"image\", \"src\": \"/images/projects/flo/bank-connection.png\", \"caption\": \"Bank connection onboarding - guided 5-step Open Banking consent flow with real-time sync progress.\"}, {\"type\": \"image\", \"src\": \"/images/projects/flo/dashboard.png\", \"caption\": \"Main dashboard - cashflow trend, spending by category, connected accounts, and recent transactions in one view.\"}, {\"type\": \"image\", \"src\": \"/images/projects/flo/accounts.png\", \"caption\": \"Accounts page with a detail panel showing derived balance history, sync status, and recent activity per account.\"}, {\"type\": \"image\", \"src\": \"/images/projects/flo/transactions.png\", \"caption\": \"Transaction management - searchable, filterable list with an inline detail/edit panel for category and budget status.\"}]",[18,19],"hr",{},[21,22,24],"h2",{"id":23},"overview","Overview",[26,27,28],"p",{},"Flo is a full-stack personal finance management (PFM) app built for the Australian market, designed around real Open Banking (Consumer Data Right) integration. It handles bank connection and transaction sync, a dashboard with spending insights, transaction management, custom categories, and manual cash account tracking - the same feature set and architecture expected of a production PFM product.",[26,30,31],{},"I built the entire product solo across 43 completed, issue-tracked milestones, from initial project scaffolding through to a fully working dashboard and transaction system.",[33,34,37],"callout",{"title":35,"type":36},"Project Scope","info",[26,38,39],{},"This is a personal showcase project built from scratch to demonstrate full-stack product engineering: real third-party financial API integration, timezone-correct data handling, a custom dashboard and charting layer, and navigating a real-world regulatory constraint.",[18,41],{},[21,43,45],{"id":44},"my-role","My Role",[47,48,50],"contribution",{"role":49},"Solo Full-Stack Developer",[51,52,53,57,60,80,87,90,93],"ul",{},[54,55,56],"li",{},"Designed and built the entire product end to end - architecture, backend, frontend, and data model",[54,58,59],{},"Integrated Basiq's Open Banking API for bank connection, auth, and transaction sync",[54,61,62,63,67,68,71,72,75,76,79],{},"Built a ",[64,65,66],"code",{},"NestJS"," + ",[64,69,70],{},"Fastify"," backend with ",[64,73,74],{},"Prisma","/",[64,77,78],{},"PostgreSQL",", covering accounts, transactions, categories, and dashboard aggregation endpoints",[54,81,82,83,86],{},"Built the ",[64,84,85],{},"Next.js 15"," frontend, including the dashboard, onboarding flow, transaction management, and settings",[54,88,89],{},"Implemented timezone-aware date handling across the full stack",[54,91,92],{},"Designed a custom category system supporting both system and user-defined categories",[54,94,95],{},"Shipped and tracked 43 features issue-by-issue on GitHub",[18,97],{},[21,99,101],{"id":100},"problem","Problem",[26,103,104],{},"Personal finance apps are only useful if they can reliably connect to real bank accounts - pulling in transactions, deriving balances, and giving people an accurate, real-time view of their spending. In Australia, that means integrating with the Consumer Data Right (CDR), the regulatory framework behind Open Banking.",[26,106,107],{},"To pull live bank data, a provider needs either direct accreditation from the ACCC, or to operate as a \"Representative\" under an already-accredited provider (an Accredited Data Recipient, or ADR - Basiq and Frollo are examples). The Representative pathway is meant to be the accessible route for smaller developers, but in practice both providers reserve it for established businesses with a commercial relationship, not individual developers building a portfolio project. I approached both and was declined on that basis.",[18,109],{},[21,111,113],{"id":112},"solution","Solution",[26,115,116],{},"Rather than treat the regulatory wall as a dead end, I built Flo to the same standard it would need for a real launch, and pointed the one component that legally required enterprise accreditation - the live bank connection - at Basiq's sandbox environment instead.",[26,118,119],{},"Everything upstream of that connection is production-grade:",[51,121,122,125,128,131],{},[54,123,124],{},"A real OAuth-style Open Banking consent and auth flow",[54,126,127],{},"Real transaction sync with pagination and idempotent upserts (no duplicate transactions on repeated syncs)",[54,129,130],{},"Real balance derivation, scoped only to account types where it stays accurate",[54,132,133],{},"A 15-minute cron-based background sync, matching how a live integration would run",[26,135,136],{},"The result is a fully working app that behaves exactly as it would in production, just connected to test institutions instead of real banks.",[18,138],{},[21,140,142],{"id":141},"application-features","Application Features",[144,145,147],"h3",{"id":146},"authentication","Authentication",[51,149,150,153],{},[54,151,152],{},"Sign up, sign in, and OAuth via Clerk",[54,154,155],{},"Email verification and password reset flows",[144,157,159],{"id":158},"bank-connection","Bank Connection",[51,161,162,165,168],{},[54,163,164],{},"Guided multi-step onboarding: select institution, consent, mobile verification, connect, sync",[54,166,167],{},"Basiq-powered Open Banking auth and 90-day historical transaction fetch",[54,169,170],{},"Automatic 15-minute background sync via cron",[144,172,174],{"id":173},"dashboard","Dashboard",[51,176,177,180,183,186],{},[54,178,179],{},"Summary stat cards (income, expenses, net savings)",[54,181,182],{},"Cashflow trend chart with Detailed/Summary toggle and period navigation",[54,184,185],{},"Spending-by-category donut chart",[54,187,188],{},"Connected accounts and recent transactions at a glance",[144,190,192],{"id":191},"transactions","Transactions",[51,194,195,198],{},[54,196,197],{},"Searchable, filterable transaction list (date range, account, category, type)",[54,199,200],{},"Detail panel with inline editing, category reassignment, and exclude-from-budget toggle",[144,202,204],{"id":203},"accounts","Accounts",[51,206,207,210],{},[54,208,209],{},"Connect, disconnect, and sync individual accounts",[54,211,212],{},"Account detail panel with a 30-day derived balance history (transaction/savings accounts only)",[144,214,216],{"id":215},"categories","Categories",[51,218,219],{},[54,220,221],{},"System-defined categories plus fully custom user-defined categories with icon and color pickers",[144,223,225],{"id":224},"manual-accounts","Manual Accounts",[51,227,228],{},[54,229,230],{},"Cash account tracking alongside connected bank accounts, with full add/edit/delete support",[18,232],{},[21,234,236],{"id":235},"technical-highlights","Technical Highlights",[238,239,242],"decision",{"title":240,"tradeoff":241},"Sandbox-first architecture for a blocked live integration","No real bank data → identical engineering to a production Open Banking integration",[26,243,244],{},"Rather than simplify the integration once live CDR access proved unavailable, I kept every part of the sync pipeline - auth, pagination, idempotency, cron scheduling - built to production standard against Basiq's sandbox. The only substitution is the data source, not the engineering.",[144,246,248],{"id":247},"timezone-correct-data-architecture","Timezone-Correct Data Architecture",[51,250,251,258],{},[54,252,253,254,257],{},"Every request carries an ",[64,255,256],{},"x-timezone"," header rather than storing timezone in the database, where it would go stale",[54,259,260],{},"Clear separation between UTC boundary helpers (used for Prisma queries) and display/grouping helpers (used for chart bucketing)",[144,262,264],{"id":263},"backend-grouped-analytics","Backend-Grouped Analytics",[51,266,267],{},[54,268,269],{},"Cashflow chart summaries (weekly/fortnightly/monthly) are grouped on the backend rather than shipping raw daily records to the client, keeping payloads small and chart rendering fast",[144,271,273],{"id":272},"idempotent-sync-pipeline","Idempotent Sync Pipeline",[51,275,276,279],{},[54,277,278],{},"Transaction sync upserts on a unique Basiq transaction ID, so overlapping or repeated syncs never create duplicate records",[54,280,281,282,67,285,75,288,291,292,295],{},"Compound-unique constraints involving nullable fields handled with explicit ",[64,283,284],{},"findFirst",[64,286,287],{},"create",[64,289,290],{},"update"," logic, since Prisma's ",[64,293,294],{},"upsert"," doesn't support nullable fields in compound uniques",[144,297,299],{"id":298},"derived-balance-history","Derived Balance History",[51,301,302,305],{},[54,303,304],{},"Open Banking data doesn't include historical balance snapshots, so balance history is derived by walking backwards from the current balance through stored transactions",[54,306,307],{},"Scoped to transaction/savings accounts only - credit cards, mortgages, and loans are excluded, since large repayments distort the derived trend",[18,309],{},[21,311,313],{"id":312},"what-this-project-demonstrates","What This Project Demonstrates",[51,315,316,319,322,325,328],{},[54,317,318],{},"Solo ownership of a complete, multi-milestone product - from data model to polished UI",[54,320,321],{},"Working with a real third-party financial API, including its auth quirks, pagination, and idempotency requirements",[54,323,324],{},"Timezone-correct data handling across a full-stack application",[54,326,327],{},"Backend-driven analytics design for fast, chart-ready aggregation",[54,329,330],{},"Recognizing and adapting to a regulatory constraint outside my control, without compromising the quality of the engineering",[18,332],{},[21,334,336],{"id":335},"impact","Impact",[26,338,339],{},"Flo is a complete, production-architected PFM application built solo, covering authentication, real Open Banking integration patterns, a full dashboard, transaction management, and custom categorization. Live bank data access was blocked by CDR accreditation requirements reserved for established businesses - not a gap in the engineering - and the project stands as a demonstration of shipping real-world financial software end to end.",[18,341],{},[21,343,345],{"id":344},"links","Links",[51,347,348],{},[54,349,350],{},[351,352,356],"a",{"href":353,"rel":354},"https://github.com/vortizz/flo",[355],"nofollow","GitHub Repository",{"title":358,"searchDepth":359,"depth":359,"links":360},"",2,[361,362,363,364,365,375,381,382,383],{"id":23,"depth":359,"text":24},{"id":44,"depth":359,"text":45},{"id":100,"depth":359,"text":101},{"id":112,"depth":359,"text":113},{"id":141,"depth":359,"text":142,"children":366},[367,369,370,371,372,373,374],{"id":146,"depth":368,"text":147},3,{"id":158,"depth":368,"text":159},{"id":173,"depth":368,"text":174},{"id":191,"depth":368,"text":192},{"id":203,"depth":368,"text":204},{"id":215,"depth":368,"text":216},{"id":224,"depth":368,"text":225},{"id":235,"depth":359,"text":236,"children":376},[377,378,379,380],{"id":247,"depth":368,"text":248},{"id":263,"depth":368,"text":264},{"id":272,"depth":368,"text":273},{"id":298,"depth":368,"text":299},{"id":312,"depth":359,"text":313},{"id":335,"depth":359,"text":336},{"id":344,"depth":359,"text":345},"md",false,{},true,"/projects/flo",{"title":5,"description":358},9,"projects/flo","Full-stack Australian personal finance tracker with real Open Banking integration and 43 shipped features",[394,66,78,74,395,396,70,397,398,399],"Next.js","Clerk","Basiq API","TypeScript","Docker","React Query","W71cW3nYMiQ1EyvCUnV4wNhzVgD0Owcei-AUHwiBrME",1784086399788]