Norway Salmon
Ordering and operations for an importer that flies fresh Norwegian salmon to Ulaanbaatar: a website with scroll-driven 3D scenes, flight-based orders paid with QPay, and an admin for orders, markets and finance.
Done

What it is
Norway Salmon imports fresh Atlantic salmon from Norway to Ulaanbaatar: skinless, boneless E-trim fillets flown chilled at 0–2 °C from Oslo via Incheon and delivered the evening the flight lands. It also sells frozen and packaged products, such as sushi boxes, through partner markets. I built the whole platform: a Turborepo monorepo with a NestJS GraphQL API, a public website, an admin and a customer assistant, in Mongolian and English.
The website in 3D
- How it is prepared. A scroll-driven 3D scene follows a whole salmon through nine cutting and packing steps, from the gutted fish to the trimmed fillet. Scrolling scrubs a baked animation clip from a GLB model.
- Oslo to Ulaanbaatar. A second scene flies a plane across a 3D globe from Oslo (OSL) via Incheon (ICN) to Chinggis Khaan airport (UBN), with airport labels.
- Built to stay light. React Three Fiber renders on demand only when the scroll moves, the models download with a progress bar into a shared cache, studio lighting comes from light formers instead of a downloaded HDR, and a slow or failed download falls back to still images. Reduced motion gets a skip link for each part.
Ordering
- Guest orders, no account. A customer gives a phone number, picks delivery or pickup, chooses one of the open flights and the products, and pays a fixed 10,000₮ deposit by QPay. The rest is paid by weight once the fish is weighed.
- Live payment status. The order page hears about the payment over a GraphQL subscription, with polling as a fallback.
- Partner markets. A page lists the markets that sell the packaged products, with a map, addresses and phone numbers.
- Assistant. A chat on every page answers customers' questions from the FAQ with Claude. It never places or changes orders.
The admin
- Dashboard. Sales collected, orders, average order value, new customers, a sales chart, the open work that needs attention, top products and the next flight arrivals.
- Orders. Each order goes from pending to paid: edit items, enter weights, deliver, then record the payment. Orders are grouped by flight, and a delivery map shows every order still to be delivered at its address.
- Markets. Partner markets, their products, daily sales sheets and a sales report.
- Finance. Income, expenses, net profit by month, VAT-reported sales, market commission, transactions, categories and loans with repayments.
- Team and settings. Admin, staff (without money rights) and customer accounts, settings, contacts and feedback. A paid invoice shows up live.
Behind it
NestJS with GraphQL, PostgreSQL through Drizzle, Redis for caching, rate limits and the subscription pub/sub, S3 storage, QPay, CallPro SMS codes and Sentry. The assistant is a Python FastAPI service behind the API. It runs in Docker behind nginx on a VPS, with backups. The admin screenshots show development data.








