Full-Stack Developer · Perth, WA

Morgan
Then

I came to development through an unconventional path and I think that shows in how I approach problems. Commerce graduate, ARIA-awarded music producer, and now a full-stack developer with three live projects and a genuine love for building things.

Technical Skills
Languages
TypeScript
JavaScript (ES6+)
HTML5 · CSS3
Frontend
React
Next.js (App Router)
Server Actions
Tailwind CSS · shadcn/ui
Backend & Data
Supabase
MongoDB
PostgreSQL · Raw SQL
Supabase Auth
Node.js · Express
Tooling & Deployment
Git · GitHub
Vercel (CI/CD)
Zod · React Hook Form
Prisma ORM
Projects
01 / 04

Remit

Invoicing web app

  • Full-stack invoicing app built with Next.js, TypeScript, Supabase (auth + PostgreSQL), shadcn/ui and Tailwind. Client and invoice management with validated forms powered by Zod and React Hook Form.
  • Each invoice carries a status — draft, sent, unpaid, paid, overdue and void, which conditionally renders an action menu, keeping the UI practical about what actions actually make sense.
  • Shareable via a unique public URL, a pre-filled mailto action, and a browser-native Save to PDF flow. Chose void-over-delete after researching how professional invoicing tools handle financial records — invoices are immutable by design for better tax auditing trails.
Next.jsTypeScriptSupabasePostgreSQLshadcn/uiZodVercel
GITHUB REPO
02 / 04

Lullo

AI-narrated bedtime story generator

  • Built with Next.js, TypeScript, Supabase, Stripe, ElevenLabs and the Anthropic API. The Anthropic model writes the story and ElevenLabs narrates it in natural voices, all sitting behind OAuth on a multi-tenant Supabase account model. Two AI APIs chained inside one user flow.
  • Full Stripe subscription lifecycle handled end-to-end through signed webhooks. Subscribe, cancel, resume and period-end events all update the database and the UI consistently, with a grace-period state on failed payments. Designed the billing layer to be portable so the same pattern can drop into future projects without rewiring.
  • Live demo runs in Stripe test mode by design so you can walk the full subscription flow without entering real card details — use 4242 4242 4242 4242 with any future expiry and any 3-digit CVC. KYC is complete and live-mode is staged for actual product launch outside the portfolio context.
Next.jsTypeScriptSupabaseStripeWebhooksAnthropicElevenLabsOAuth
GITHUB REPO
03 / 04

Oculé

Speed-reading web app + Chrome extension

  • Built with React, TypeScript, Vite, Tailwind CSS and shadcn/ui. Uses Rapid Serial Visual Presentation to display text one word at a time at a fixed focal point, cutting saccadic eye movement out of the reading process so the same content goes through faster.
  • Eased punctuation pauses keep the reading rhythm natural so it doesn't feel mechanical. Font size and words-per-minute are adjustable mid-session, with preferences persisted via localStorage. No backend and no accounts — fully client-side by design because the input is the user's own text.
  • Paired with a companion Chrome extension published on the Chrome Web Store with real external installs. The extension is a parser — point it at any article (e.g. a Wikipedia page), it extracts the relevant body text and hands off to ocule.app to read it. Extension source lives at github.com/morganthen/ocule-extension.
ReactTypeScriptViteTailwindChrome ExtensionRSVPWeb Store
GITHUB REPO
04 / 04

Droptop

Bookmark manager

  • Built twice on purpose. V1 used Node.js and Express with MongoDB and a separate Vite frontend — the full split-repo setup, CORS config and all. V2 is the same app rebuilt in Next.js with PostgreSQL and Prisma. Doing it the long way first meant Next.js API routes stopped feeling like magic and started making sense as a layer over the same request/response pattern.
  • Automatically fetches page title, description and cover image via Open Graph scraping on URL blur — the same mechanic Pinterest uses when you save a pin. Database is PostgreSQL managed directly through Prisma 7, which has significant breaking changes from v6: the connection URL moved to a separate prisma.config.ts file and a new driver adapter is required. Most online tutorials were written for v6, so the official docs did the heavy lifting.
  • Auth is JWT and bcrypt, rolled by hand. Having implemented it in Express first, doing it again in Next.js was about reinforcing the mental model rather than learning something new. The main difference is exporting named GET/POST/DELETE functions that return Response objects instead of calling res.send().
Next.jsTypeScriptPostgreSQLPrismaJWTOpen GraphTailwind
GITHUB REPO
About

Built to figure it out

I came to development after a decade music production, audio engineering and touring internationally. Over the years, I've learned how to manage complex projects, meet tight deadlines, and communicate clearly with non-technical stakeholders.

I am fluent in English, Mandarin (and it's various dialects) and Bahasa Malaysia and I hold a triple major Bachelor of Commerce from the University of Western Australia in Managerial Accounting, Finance and Business Economics. I approach code the same way I approached music and finance: understand the system, know what it's trying to do, then build something that actually works for the people using it.

I've been building seriously for just over a year and have three full-stack TypeScript projects live in production. I'm looking for a team where I can keep growing fast.

Bachelor of Commerce — UWATriple major: Managerial Accounting, Finance & Business Economics
freeCodeCampResponsive Web Design + JS Algorithms & Data Structures — 2025
App BreweryFull-Stack Bootcamp — 2025
2 x ARIA AwardProducer on Daniel Johns (Silverchair) 'FutureNever' album. Highest selling Australian album of 2022
3× Gold-Certified RecordsAustralian Recording Industry Association
3× Western Australia Music AwardBest Independent Album
2× National Live Music AwardAIR Awards 2018
Contact

Let's build
something good

I'm open to junior positions and internships with teams who build things they're proud of. If that sounds like you, I'd love to chat.