Internal tool · design + build · not public
Sites CRM


Challenge
Every site I run drags a tail of small obligations behind it: a domain at one registrar, hosting at another, mailboxes somewhere else again, the credentials for all of it, and a payment date attached to each. Scattered across invoices, registrar emails and memory, that tail only becomes visible once something has already lapsed — and a domain that lapses quietly is the most expensive mistake available in this line of work.
What was built
One panel over all of my own projects. Each site lists its services — domain, hosting, mail — with the provider behind each one, the access it needs, the date it renews and what it costs. The dashboard opens on whatever actually demands attention: what is overdue, what expires within thirty days, and which charges will fire automatically without anyone confirming them. Below that, a renewal horizon lays two years out month by month, so a cluster of renewals landing together is visible long before the invoices are. Every site carries its own yearly total, and the panel adds those into one running cost of keeping the whole set online.
Technical decisions
Next.js with the App Router and TypeScript, so every service, provider and payment is a typed record rather than a spreadsheet row that silently accepts anything. Data and authentication both sit on Supabase — Postgres for the records, Supabase Auth for access to them. Dates do the real work: time remaining, notice periods and automatic charges are all derived from the one renewal date stored per service, which is what makes a two-year horizon renderable at all without keeping a second calendar by hand next to it.
The one deliberate omission is a public address. The panel holds client names, supplier accounts and real payment amounts, so it is not deployed anywhere a visitor can reach it: this case study shows what it does and how it is built, and stops there. That is also why this page carries no link out to a live site — there is nothing here that could safely be linked to.