Emmanuel Doji

Portfolio · 2026

000%

EmmanuelED · 26
All case studies

Pagrin · 2025 — Present · Frontend engineer

Pagrin — Frontend Across a Multi-App Turborepo

Apps in workspace
2
Shared packages
6
Locales supported
next-intl
Analytics adapter
PostHog

My role

I lead the frontend across the Pagrin codebase. The partners dashboard — where Pagrin partners manage their integrations, monitor signals, and talk to the AI chat agent — and the embeddable web SDK that ships Pagrin into third-party surfaces both flow through my work.

The structure I shipped

The repo is a Turborepo with two apps and six shared packages:

apps/
  partners-dashboard     Next.js 16 · next-intl · @pagrin/ai-chat
  web-sdk                Next.js 16 · embeddable surface

packages/
  @pagrin/ai-chat        React component + hooks for the support / partner agent
  @pagrin/analytics      PostHog-backed event layer
  @pagrin/i18n           Typed translation keys + drift-detection scripts
  @pagrin/ui             Design-system primitives (Tailwind v4)
  @pagrin/eslint-config  Shared lint
  @pagrin/typescript-config  Shared tsconfig

A change to the chat UI or the analytics layer ships to both apps in one PR. The dependency graph is explicit in turbo.json, so CI rebuilds only what changed — cold build is ~45s, Turbo-cache hit is ~3s.

Design decisions that mattered

Why a monorepo, not a Git submodule story? Submodules push the coordination tax onto every PR. Workspaces make the apps consume @pagrin/* at source — no publish step, no version drift, no "this works on main but not on the dashboard branch."

Why next-intl over react-i18next? App Router locale routing was a first-class citizen, and the static-rendering story is significantly better. The @pagrin/i18n package wraps it with typed keys and a check.mjs script that fails CI on missing translations across locales.

Why ECharts in the AI chat package? The agent renders inline charts when it answers data questions. ECharts gives us declarative chart specs that can be serialized over the wire from the model — Recharts and Chart.js would have required a heavier React-only path.

What the work feels like

Most days I'm either building a new surface in the dashboard (recently: an xy-flow canvas for visualizing partner integration topology) or extending one of the shared packages because both apps need the same primitive. The leverage compounds — every package I harden pays out in both apps for the rest of the product's life.