Skip to content
All WorkB2B+B2C Marketplace · Design System

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.

Product DesignMarketplaceDesign System

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

Story 01

As — I want — so that

Story 02

As — I want — so that

Story 03

As — I want — so that

Story 04

As — I want — so that

Story 05

As — I want — so that

Story 06

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

Legal without TIN — moderation before first publish, demo without verified badgeSwitch account type private ↔ legal in account, legal → private cleans employeesFirst warehouse free auto, changing private city paid or once per 6 monthsCannot create listing without country/city/region, street optional, model/generation optionalNew parts only from trusted feeds, auth Basic + proxy php for HydraParts10 listings per page, Show more + numbered pages, header New arrivals vs Found XSorting: price/year/relevance, without price at end, no distance sorting in MVPTwo currencies in price filter from–to, conversion via single util, not raw rendering

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.

buyerBuyeruser
privatePrivate 1 cityuser
legalLegal N citiesuser
employeeEmployee to warehousesuser
warehouseWarehouse city/country/regionsystem
phonePhones per warehousesystem
listingListing 4 statessystem
importImport XML/JSON normsystem
pricePrice getPriceDisplaysystem
hydraHydraParts XML proxyexternal
avtolotAvtolot JSONexternal

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

01

Warehouse as entity, phones inside warehouse

Screen placeholder
02

One import normalizer for XML and JSON

Screen placeholder
03

Creation wizard with different required rules

Screen placeholder
04

Listing and currencies in one util

Screen placeholder
05

Tariffs and paywall as place, not screen

Screen placeholder

Screens & flows

Screen 1
Screen 2
Screen 3

Outcome & next

01

02

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.