Loading

All projects

Web App · 2025

Snapz Dashboard

Snapz tracked every campaign in spreadsheets. We replaced that with a multi-tenant analytics platform: realtime event ingestion, role-based workspaces, Stripe billing and exportable reports — all behind a single sign-in.

ReactTypeScriptPostgreSQL

Client

Snapz Media

Industry

SaaS / Analytics

Timeline

9 weeks

My role

Lead Full-Stack Developer

The problem

The team ran their entire operation from spreadsheets, so every report was a day old and permissions were impossible to manage.

The solution

A realtime dashboard with role-based access, live charts, billing and audit logs — replacing six spreadsheets and two manual reports.

-90%

Reporting time

4.2k

Daily events tracked

99.9%

Uptime since launch

What I built

Realtime charts

WebSocket stream pushes new events into the chart layer without a refetch.

Role-based access

Owner / admin / viewer roles enforced in Postgres row-level security, not just the UI.

Usage billing

Stripe metered subscriptions with in-app plan upgrades and invoice history.

Audit log

Every destructive action is written to an append-only table with actor and diff.

Architecture

  • React + TypeScript SPA with TanStack Query for cache and optimistic updates
  • Node/Fastify API, Zod-validated at every boundary
  • PostgreSQL with RLS + TimescaleDB hypertable for event data
  • Redis pub/sub fanning realtime updates to connected clients
  • Deployed on Fly.io with GitHub Actions CI, preview envs per PR

Hard problems

4.2k events/min ingestion

Writes were batched into 1-second windows and flushed with COPY, dropping DB CPU from 80% to 12%.

Charts froze on large ranges

Server-side downsampling (LTTB) caps every series at 500 points, so a 1-year range renders as fast as a day.

Code from the build

A few real excerpts from the repository.

src/server/events/ingest.ts
const buffer: EventRow[] = [];

export function queueEvent(row: EventRow) {
  buffer.push(row);
  if (buffer.length >= 500) void flush();
}

setInterval(flush, 1000);

async function flush() {
  if (!buffer.length) return;
  const batch = buffer.splice(0, buffer.length);
  await db.copyFrom("events", batch);   // ~30x faster than row inserts
  redis.publish("events:new", JSON.stringify({ count: batch.length }));
}
supabase/policies.sql
create policy "workspace members read events"
on public.events for select
to authenticated
using (
  exists (
    select 1 from public.memberships m
    where m.workspace_id = events.workspace_id
      and m.user_id = auth.uid()
  )
);
"We closed six spreadsheets on launch day. Reporting that took an afternoon now takes one click."
Hassan T. · Operations Lead, Snapz Media

How it was built

  • Discovery call, scope and fixed timeline agreed up front
  • Custom design system built in Figma before development
  • Typed, reviewed codebase with CI checks on every commit
  • Deployed with monitoring, backups and post-launch support

Want something like this?

Start a Project