Portfolio/SparkPlug DMS
🚗 Dealer Management · VIN Decode + User Admin · React + Tailwind

SparkPlug DMS

A dealer-management tool for US car dealerships. Paste a VIN and get the full NHTSA factory record, 150-plus fields with everything the database is missing flagged, then manage who can use the system through Keycloak.

Role
Product Design + Frontend
Timeline
2024 – Present
Type
B2B SaaS · in build
Platform
Web · React + Tailwind
app.sparkplugdms.com/vin
SparkPlug DMS
Home
VIN
Users

Search VIN

JTHBK1EG2A2385435
Vin Overview
Logo not available

LEXUS ES (2010)

Available data 33%

japanModel ESYear 20106 Cylinders HPGasoline

All Data

ABS

Not found

ActiveSafetySysNote

Not found

AdaptiveCruiseControl

Not found

AdaptiveDrivingBeam

Not found

AdaptiveHeadlights

Not found

AirBagLocCurtain

All Rows

AirBagLocFront

1st Row (Driver & Passenger)

AirBagLocKnee

1st Row (Driver & Passenger)

AirBagLocSide

All Rows

BodyClass

Sedan/Saloon

Doors

4

DriveType

FWD

EngineCylinders

6

EngineModel

2GR-FE

FuelTypePrimary

Gasoline

Make

LEXUS

Manufacturer

TOYOTA MOTOR MANUFACTURING

Model

ES

ModelYear

2010

PlantCountry

JAPAN

Series

Not found

TransmissionStyle

Not found

VehicleType

PASSENGER CAR

WheelBaseShort

Not found

A VIN, decoded.
Access, controlled.

Small US used-car dealerships run on a stack of browser tabs and a shared spreadsheet. They type the same 17-character VIN into a free lookup site, then re-key the spec by hand. The public data is all there in the NHTSA database; it just comes back as a wall of 150-plus fields, most of them empty for an older car.

The strategy is a wedge, not a suite. VIN decode is the hook, a daily job it does better than the free tools, and the platform it opens onto is inventory and a sales-lead pipeline. Get a dealer decoding VINs in SparkPlug every morning and you’ve earned the right to be where their inventory lives too.

SparkPlug DMS turns the raw decode into something you can read: a summary with make, model, year and a colour-coded Available data %, quick spec chips, and the full record behind one expandable card where every missing field is marked Not found rather than dropped. Access is handled by Keycloak: real login, realm roles, and a Users screen to enable, disable, edit or remove people.

I own product design and the front end, working against a two-backend API (a NestJS passthrough and a .NET Clean-Architecture service). It’s in active build: the decode and identity core is done; inventory and the lead pipeline are next.

TypeB2B SaaS, in build
ScopeProduct design through front-end build
Front endReact 19, TypeScript, Tailwind v4, Vite
AuthKeycloak · OIDC, realm roles
VIN dataNHTSA vPIC · DecodeVinValuesExtended
Back endNestJS passthrough + .NET (Clean Architecture)
AreasHome · VIN · Users

Win the first click,
then earn the next one.

Independent dealers already have a system of record they will not rip out. The way in is not a better suite, it is being indispensable for one job they do every day.

01
The user
A 5 to 20-car independent lot, one or two people doing intake, listings and sales. No IT, no training budget. If a screen needs a manual, it has failed.
02
The wedge
Be the fastest, most readable VIN decode they can get, then own the very next action: add this car to inventory. Every design choice on the VIN screen is about getting a dealer from paste to a stocked vehicle without a detour.
03
The non-goals
No accounting, no title and DMV paperwork, no marketing syndication, no mobile app in v1. Those are where incumbents are strongest and switching cost is highest. SparkPlug earns the right to go there later by being useful now.

How I judge the core. A dealer who has never seen SparkPlug pastes a VIN, understands within seconds what the record is missing, and adds the car to inventory without asking anyone. And the front end builds and runs green with no backend at all, so the product can be shown and iterated anywhere.

Read the market,
then read a spec sheet.

I went through the free VIN sites and the incumbent dealer systems on the same task. The free tools dump raw NHTSA fields with no hierarchy; the incumbents bury the decode three screens deep behind a full inventory workflow. Nobody makes the decode itself fast and legible, which is the opening.

Watching how a dealer actually reads a spec sheet set the key interaction. They scan for specific facts, drivetrain, engine, airbags, and they need to know instantly whether a fact is missing from the data or the car genuinely does not have it. That is why every empty field is a visible Not found rather than a hidden row, and why the coverage percentage sits at the top: it tells them whether this decode is worth trusting before they read 150 lines.

Two constraints were set by the client’s reality, not by me: an existing .NET team and a running Keycloak instance. So identity was bought, not built, and the front end was designed to not care which of the two backends answered a request.

ComparedFree VIN sites, incumbent DMS
GapNobody makes the decode readable
ObservedHow a dealer reads a spec sheet
Led toNot-found flags + coverage score
Given.NET team, Keycloak already running
Led toBuy identity, one contract, two backends

Three things
in the way.

01
A VIN record is mostly blank
NHTSA returns 150-plus fields, and for anything but a current-year car most come back empty. A raw dump is unreadable and hides the few facts that matter.
02
Auth is not a feature to hand-roll
Role-gated dealership tooling needs real identity (login, realm roles, enable and disable), not a bespoke users table and a password column.
03
Two backends, one contract
A NestJS passthrough and a .NET Clean-Architecture API both have to hand the SPA the exact same shape, or the front end branches forever.

Tailwind primitives,
then the screens.

01
One Tailwind system, no UI kit
Plain Tailwind v4 and a small set of hand-built primitives (a Chip with filled / soft / outlined variants, the tab bar, the card shell) so every screen is assembled from the same parts without pulling in a component library.
Tailwind v4Design PrimitivesChip / Tabs / Card
02
VIN decode, made readable
Validate the VIN, call NHTSA vPIC, then lead with a summary: make, model and year, an Available-data % that turns orange / blue / green by how complete the record is, and a row of quick spec chips.
NHTSA vPICValidationCoverage score
03
All Data, on demand
The full 150-field record sits behind one expandable card in a three-column grid. Every empty field shows a Not found badge rather than being dropped, and each row has an inline edit.
Expandable card3-column gridInline edit
04
Keycloak for identity
Login, realm roles and a Users screen that lists everyone from the Keycloak admin API and can enable, disable, edit or remove them, with status and roles shown as chips on every row.
KeycloakOIDCRealm rolesUser admin
05
Backend-optional dev
Two flags, VITE_AUTH_DISABLED and VITE_VIN_DIRECT, let the SPA mount with a mock signed-in user and call NHTSA straight from the browser, so pnpm dev works with no identity server and no backend at all.
DXEnv flagsMock auth

Four decisions
behind the build.

Each one traded speed or simplicity now for something the product needs later.

01
Show the gaps, don't hide them
The clean move is a tidy summary that drops empty fields. I show every one instead, each marked Not found. It makes the All Data card noisier, but a dealer needs to see exactly what they still have to source by hand, and hiding the blanks would quietly imply the record is complete when it isn't.
Data honestyTrustInformation density
02
Keycloak, not a users table
Rolling a bespoke login and a password column would have shipped the first demo faster. But this is role-gated tooling that will need SSO, audit and de-provisioning. Keycloak gives all of that now. The cost is an OIDC redirect flow and an identity server to run, which is the right liability to take on early rather than retrofit.
IdentityOIDCBuild vs buy
03
One DTO shape from two backends
A NestJS passthrough and a .NET service both feed the SPA. The easy path lets the front end adapt to whichever one answered. I held a single response contract instead, with the passthrough normalising NHTSA's shape. That is more coordination up front, but the front end never branches on which backend it's talking to.
API contractFront-end simplicityCoordination
04
The app runs with no backend at all
Two flags, VITE_AUTH_DISABLED and VITE_VIN_DIRECT, let the SPA mount with a mock signed-in user and call NHTSA straight from the browser. It's a second code path to keep honest, but it means design iteration and a new dev's first run don't wait on infra being up.
DXIteration speedEnv flags

Six things
it does.

🔎
VIN decode
Paste a 17-character VIN and get the full NHTSA vPIC factory record back in one call. Works with or without the SparkPlug backend.
📊
Available-data score
A single percentage for how complete the record is, colour-coded orange below 50, blue to 70, green above.
🏷️
Not-found flags
Every field the database doesn't have is shown with an orange Not found badge, so you can see what's missing at a glance.
🗂️
All Data grid
The whole 150-plus-field record behind one expandable card, in a three-column grid, with an inline edit on every row.
🔐
Keycloak auth
Real OIDC login with realm roles, plus a dev flag to skip it entirely and mount with a mock signed-in user.
👥
User management
A Users screen backed by the Keycloak admin API: status and roles per row, and enable / disable / edit / remove.

A closer look.

Core built,
end to end.

The mockups on this page are rebuilt from the real front end (React + Tailwind); the decoded values shown are an illustrative NHTSA-style record, not live data.

150+
Fields per VIN decode
Straight from the NHTSA vPIC database
3
Areas in the app
Home, VIN, Users
1
Identity system
Keycloak · OIDC, realm roles
2
Backends, one contract
NestJS passthrough + .NET API
0
Backend needed in dev
Direct-NHTSA and mock-auth flags
3
Colour bands on coverage
Orange < 50, blue ≤ 70, green above

What’s next, and what I’d watch. The Available-data % is a proxy, not a verdict: a current-year car can read low simply because NHTSA hasn’t populated it yet, and the UI shouldn’t make that look like a bad VIN. The real value is still ahead: inventory and the lead pipeline are where a dealer’s day actually happens, and the decode is only the wedge that gets SparkPlug onto their screen.

Next Project
SpendWell
A personal-finance app that turns messy bank exports and paper receipts into clear, categorised spending.
View project →