B2B+B2C Marketplace · Design System · May 2025 – May 2026
Auto Parts Marketplace
B2B+B2C marketplace for used and new auto parts across CIS — from private sellers with 1 phone in 1 city to dismantlers with 20 warehouses. IA around 3 roles, warehouse as entity with phones[] per warehouse, unified XML/JSON import, dual-currency via single util. In development, no vanity metrics.
Role
Lead Product Designer — UX/UI, IA, Design System, PM support
Timeline
May 2025 – May 2026
Team
UX/UI | Product designer · Frontend-developer · Backend-developer
Platform
Web
Задача
CIS auto parts search is brand → model → generation → year → part. Two seller types. Private: 1 city, 2–3 phones, 1 warehouse free by default. Legal: N warehouses across different cities, phones per warehouse, employees tied to warehouses, N listings. Goal — design end-to-end so one flow survives both scenarios: IA for 3 roles (private / legal / employee), listing card with 4 states, creation wizard with required country/city/region and optional street, search with fallback to year when generation is unknown, pricing via getPriceDisplay (primary = user choice, secondary = original), tariffs 1st warehouse free, rest paid, and a design system ready for code — Next 15 / React 19 / Tailwind 4 / next-intl + Fastify / Prisma / SQLite. MVP not launched, so no % — only facts from code and calls.
Почему появилась — триггер
Client sells on other marketplaces and runs dismantlers. Every day same pain: marketplace has no warehouse entity, phones are global, city is a string, import from new stock only manual. New parts come from two sources with different contracts — HydraParts returns XML via proxy.php with Basic auth, Avtolot returns JSON. Decision: build own marketplace where domain model matches how the business actually operates.
Как решали до
Sales went through external marketplaces. Search allowed only brand/model/year — no generations or facelifts, transitional years fell into '2003' gap. Phones were flat array on account, buyer called wrong city. Warehouse was not an object, only city string. Private sellers saw same fields as legal. Price in BYN/USD rendered as received from feed — no utils. Free limit and tariffs existed in heads, not in UI. Import only via 1C / custom tables, no unified import UX.
Research
Goal
Figure out how a real dismantler sells across 3+ cities and what blocks a private seller from listing one part. Test account → warehouses → phones split and import formats. Define baseline for listing card.
Method
Reviewed listings and creation flow on donor marketplaces. Calls with client with screen share — watched how they create warehouses and phones. Audited MVP codebase: getParsWithFilters, getPriceDisplay in utils/money.ts, auth / profiles / listings routes, schema.prisma. Parsed live feeds HydraParts XML, Avtolot JSON via proxy. Consolidated into dated notes Mar–Jul where fields country/city/region required + street optional came from.
MOC ZK0044 VPNauto + 10 notes (Business/Import/Profile/Card/Main/Backend/Frontend/Decisions/Tariffs/Design)
Notion board + calls (IDs in vault)
Figma file with wizard and listing (ID in vault)
Code: getParsWithFilters, getPriceDisplay, schema.prisma — field audit
Feeds: HydraParts XML via proxy.php + Basic, Avtolot JSON, newsite/used XML
Checklist for creation: country/city/region required, street optional
Business wants / Users want
Business
- —Split private vs legal in onboarding: legal shows company data for moderation, no publish before approval, higher trust
- —Warehouse as paid entity — 1 free auto-warehouse, extra paid, city/country/region required
- —Bulk onboarding — import XML/JSON from new parts sources, unified auth headers (Basic), proxy php for Hydra
- —Dual-currency display via single util (getPriceDisplay in utils/money.ts) not raw mixed rendering
- —Up to 1000 free listings until EOY, then tariffs — design place for paywall, not paywall itself
- —Zero-downtime SEO — brand, model, poly-model variants, year, facelift fallback to year when generation missing
Users
- —Private: list 1 part in 2 minutes, 1 city, 2–3 phones, address from profile, no warehouse picker
- —Legal: add warehouses in multiple cities, phones per warehouse, employee accounts tied to warehouses
- —Creation wizard with no hard stops: country/city/region required, street optional, car model/generation optional for universal parts
- —Move stock from 1C / custom tables / other marketplaces with one import, not manual copy
- —List view: 10 cards per page, counter shown X of Y, chips for active filters with X
- —See two currencies at once, sort by price/year/relevance, items without price at end
User stories
As — I want — so that
As — I want — so that
As — I want — so that
As — I want — so that
As — I want — so that
As — I want — so that
Hypotheses
H01
If warehouse becomes an entity with phones[], call hits right city and reduces misrouted calls
How to check
Check MVP code and calls: now phones[] on account. Mock — warehouse with phones[] and counter. Validation after launch — match call city vs warehouse, no invented % now.
H02
If private gets free auto-warehouse and 2–3 phones without warehouse picker, he will list 1 part in 2 minutes
How to check
Run wizard with 3 private scenarios in Figma prototype. Time per step, errors on country/city/region. File IDs in vault.
H03
If we normalize HydraParts XML and Avtolot JSON via one parser, new parts appear without manual re-check
How to check
Parse live responses via proxy.php, map to unified schema partNumber/brand/price/currency/stock. Debug log + mismatch list.
H04
If we fallback to year when no generation, user still finds part in facelift transition
How to check
Take transition years from donors, build matrix brand/model/year → gen missing → fallback year. Ensure listing doesn't go empty.
H05
If price always via getPriceDisplay (user currency + original), no duplicate numbers in different currencies
How to check
Unit tests utils/money.ts, screenshots listing before/after with same 500 number in BYN vs USD, check against artifacts.
Constraints
Helicopter view
Focus — warehouse as entity. Phones live inside warehouse, not profile. Private gets auto warehouse without picker, legal — N warehouses. Import normalizes different feeds into one listing schema.
Flows
private → warehouse · 1 auto free
legal → warehouse · N warehouses
warehouse → phone · phones[]
legal → employee · employees
employee → warehouse · to warehouses
warehouse → listing · binds
import → listing · creates
hydra → import · XML via proxy
avtolot → import · JSON
price → listing · BYN/USD
buyer → listing · filter year fallback
Before / After
Before
City as string, phones as array on account. Buyer calls wrong city.
After
Warehouse entity with city/country/region required, street optional. Phones as phones[] inside warehouse. Private 1 auto, legal N.
Why
From code audit and calls: people lose when no entity to attach number. Entity solves call routing.
Before
1C/custom tables manual, HydraParts and Avtolot in different places without single parser.
After
One normalizer for XML and JSON. HydraParts via proxy.php Basic, Avtolot JSON direct. Dry mappings partNumber/brand/price/currency.
Why
Feeds live, contracts different. Normalization removes parser copy-paste and gives one entry to listing.
Before
Two numbers BYN/USD as arrived from feed — side by side without explanation. One currency in filter.
After
getPriceDisplay in one place: primary — user choice large, secondary — original small. Filter shows two currencies from–to.
Why
Confusing when 500 BYN and 500 USD look identical. Single util trims check length and unifies display.
Before
One form for all, model/generation required. Universal parts cannot be added.
After
Required country/city/region, street optional. Car model/generation optional for universal parts. Private no warehouse picker, legal with picker.
Why
Different entry threshold. Private needs 2 minutes, legal needs control of N cities. Split scenarios, not screens.
Before
Brand/model/year, no chips, filters not in URL, empty list on facelift transition.
After
brand→model→gen→year→part, filter chips with X, filters in URL, fallback to year if no generation, regions as tree.
Why
Donors don't provide facelift. User loses results in transition years. Fallback keeps listing alive.
“We need one catalog that doesn't break when seller is both a private owner and a network of dismantlers. Warehouses by city, phones tied to warehouses, bulk import from 1C and external sources, dual currency that actually makes sense.”
Client
Design decisions
Warehouse as entity, phones inside warehouse
One import normalizer for XML and JSON
Creation wizard with different required rules
Listing and currencies in one util
Tariffs and paywall as place, not screen
Screens & flows
Outcome & next
MVP not launched. No percentages. Based on code and calls facts. After launch — measure private creation time, call hitting right warehouse, empty listing rate on facelift, card → call conversion.