Freelance · Web app · 2026
TEEVS
The shop ran on a Google Apps Script app and a spreadsheet. I migrated it to a full storefront and admin site on Next.js, with live market pricing and OCR that reads slab labels from a photo.
- Role
- Freelance software engineer
- Stack
- Next.js, Postgres, Live market data, Tesseract.js, Vercel
- Links
- Live site
- Live
- market pricing
- 96%
- OCR accuracy on PSA labels
- Apps Script → Next.js
- storefront and admin site
TEEVS sells graded trading cards, and when I started, the whole shop ran on a Google Apps Script app and a spreadsheet. I migrated it to a full storefront and admin site on Next.js and Postgres, priced from live market data, with OCR that reads a slab's label from a photo and gets it right 96% of the time.
From Apps Script to a storefront and admin site
The original store was one Apps Script web app over eight Google Sheets tabs: graded cards, sealed product, sales, finances, offers, trades, tags and settings. Images went to Google Drive as Base64 uploads, exchange rates were typed in by hand, and the admin login was one hardcoded password. It worked, but Sheets was slow and nothing was transactional.
I moved it in stages. The first Next.js version still read and wrote Sheets, and it ran that way for about two months. Then I put Postgres behind the same interface the Sheets code used, so the rest of the app didn't have to change, and wrote a one-off script that migrated each table in its own transaction and checked row counts afterward. 2,039 rows came over: 338 graded slabs, 18 sealed items, 332 sales, 1,246 finance entries, 86 offers and 19 tags.
What came out the other side is two apps in one. Buyers get a storefront: graded, raw and sealed cards across Pokémon, Riftbound and One Piece, filters for grading company, condition and price, and 12 currencies with rates refreshed daily. Sales happen off-platform, so there's no checkout. A buyer builds a cart or sends an offer, the API re-checks stock, and the owner gets it on Telegram. The store API names every field it sends, so cost prices never reach the response.
The owners get an admin site. Two businesses (the shop and TEEVS Co.) run the same inventory, sales, finance and analytics tools on separate databases, and TEEVS Co.'s books split by whose money each item is. Admins sign in with Google, limited to a list of emails, which replaced the shared password.
Pricing every slab from the live market
Pricing a graded card means knowing what that exact card, at that grade, is selling for right now. Every slab already has a grading company and a cert number, so the app looks each one up by cert, in batches of 70, and there's no guessing which card a slab is. The lookups run in the background, so no page ever waits on the market to load.
When a card has its own tracked market value, the app uses it. Otherwise it takes the median of recent sales, because for 68 of 301 cards the newest sale sat more than 20% off that median. The owners see it in the inventory next to their asking price, with margin against cost, population, a sales chart and current listings, and they can price a new slab on the spot.
Reading slab labels with OCR
Adding stock starts from a photo. The admin site reads the slab's label right in the browser with Tesseract.js, for PSA, CGC and TAG slabs, so no image leaves the device. I tuned it on the layout of the label itself: it finds the PSA frame by its red bands, reads the grade column on its own, and reads the cert a second time with digits only. On 24 PSA slabs it had never seen, it read the company, grade and cert correctly 96% of the time.
Every field stays editable before it's saved, saves are all-or-nothing, and duplicate certs are blocked at the database.
I shaped the codebase as a template for other card shops, and that's what I'm building on next.