Files
marketplaces/docs/ADMIN-UX-AUDIT.md

195 lines
27 KiB
Markdown
Raw Normal View History

# Admin Panel UX/Product Audit — First-Time Customer Walkthrough
**Date:** 2026-07-17
**Role played:** Senior Product Designer / UX Researcher / SaaS PM / Accessibility reviewer, evaluating the admin panel as a store owner opening it for the first time, knowing nothing about this codebase.
**Method:** Live walkthrough of every routed backoffice screen (`/:lang/backoffice/**`) via `devBypassAdmin=true`, reading rendered DOM/accessibility tree and page text, cross-checked against source (`app.routes.ts`, i18n files, guards, components). No fixes applied — audit only, per request.
**Screens covered:** Dashboard, Categories (list + create), Products (list + create), Orders (list + detail), Transactions, Users & permissions, Monitoring, Analytics, Media Library.
> Scope note: the ~178-key missing-translation bug (every `adminProducts.*`, `adminCategories.*`, `adminOrders.*`, `adminTransactions.*`, `adminUsers.*`, `adminMonitoring.*`, `adminAnalytics.*` string rendering as a raw key like `adminProducts.create`) is **already tracked** in [KNOWN-ISSUES.md](KNOWN-ISSUES.md#2) and deferred to Sprint 29. This audit does not re-report it as a new bug, but treats it as the single biggest blocker to a first-time user understanding *any* admin screen, and uses the raw keys as a proxy for the intended copy when judging IA and labeling choices below.
---
## 0. The one thing that matters most
**There is no admin shell.** No sidebar, no top nav, no breadcrumbs, no "you are here" indicator anywhere in the codebase (confirmed: no `admin-shell`/`admin-nav`/`admin-sidebar`/layout component exists at all). Every backoffice page is: the **public storefront header** (logo, catalog, search, wishlist, cart, region/currency/language switcher — all customer-facing) + a single unlabeled **"Вернуться" (Back)** button + raw page content. To get from Products to Orders, a user must click Back all the way to the Dashboard and click a link there, or hand-edit the URL.
This one gap is why almost every screen below scores badly on "can a first-time user understand where they are and where else they can go." Fixing it is the highest-leverage single change in this plan (see Task R1).
---
## 1. Screen-by-screen findings
### 1.1 Dashboard (`/backoffice/dashboard`)
1. **What is this page?** Landing page after admin login; store status + shortcuts.
2. **Goal:** Give an at-a-glance health check and jump-off points to every admin section.
3. **Understandable first-time?** Partially. The quick-actions list (translated: "Категории, Товары, Транзакции, Заказы..." etc.) is clear. The stat cards are not.
4. **Confusing:**
- Metric cards for Categories/Products say **"Не удалось загрузить карточку"** ("Card failed to load") — reads as a broken feature, not as "no data yet." A first-time user with zero products would reasonably think the dashboard is malfunctioning.
- Orders/Revenue cards say **"Ожидает интеграции с бэкендом"** ("Awaiting backend integration") — raw internal/dev language shown to an end user. No store owner should ever see the word "backend integration."
- **System status list is unreadable as a checklist.** Header says "Все проверки пройдены" (All checks passed) directly above six items — "Bootstrap корректен", "Конфигурация корректна", "Отсутствуют переводы", "Некорректные цвета", "Некорректные ссылки виджетов", "Некорректные макеты" — with **no pass/fail icon or state per item**. Two of six items are literally negative-sounding phrases ("Missing translations", "Invalid colors") sitting under an "all passed" banner with no visual distinction from the two genuinely-positive ones. A user cannot tell which of the 6 is good and which is bad without reading a color they can't see in the accessibility tree (needs live visual check — flag for a11y contrast/icon-only-signal audit too).
5. **Missing:** No onboarding / "getting started" checklist for a marketplace in Draft status with 0 products. No empty-state guidance ("Add your first product", "Publish your store"). No timestamp/freshness indicator on the stat cards so users know if "failed to load" is real-time or stale.
6. **Meaningless inputs:** None (read-only page).
7. **Technical labels:** "Версия конфигурации: 1.0.0", "Активный макет: default", "Активные виджеты: 3" — config version, layout name, and widget count mean nothing to a non-technical merchant. No tooltip/explainer.
8. **Dangerous actions:** None on this page.
9. **Missing explanations:** Every stat card is a bare label + value, zero context ("Заказы" with no count and no explanation of why").
10. **What would Shopify do:** Home screen would show a setup checklist (theme published? first product added? payment configured?), real numbers or a clean "—" with a tooltip explaining why, and would never expose the words "backend integration" or "bootstrap" to a merchant.
### 1.2 Categories — List (`/backoffice/categories`)
12. Manage the category tree; filter/search/create.
3. Not understandable today — see i18n gap (search box placeholder, visibility filter, "show deleted" checkbox, and Create button are all raw keys).
4. **Confusing beyond i18n:** the visibility filter on the *Categories* list reuses `adminProducts.*` keys (`adminProducts.allVisibility`, `.visible`, `.hidden`) — i.e. the categories filter is either copy-pasted from Products and never renamed, or is genuinely sharing the wrong translation namespace. Either way it signals categories were bolted on after products without a shared, deliberate filter component.
5. **Missing:** No bulk actions (bulk hide/delete/move), no category count-of-products column, no drag-to-reorder indicator visible from the list itself (reorder exists per git history but isn't surfaced here as an affordance).
6. **Meaningless inputs:** "Show deleted" checkbox with a plain checkbox + label, no explanation of what "deleted" means here (soft-delete? Restorable? For how long?).
9. **Missing explanations:** None of the destructive-adjacent controls (deleted-items toggle) explain retention/recovery.
10. **Shopify:** Category/collection list has search, a clear empty state with a CTA, and inline reorder — no separate hidden filter-vs-products confusion.
### 1.3 Category editor — Create/Edit (`/backoffice/categories/create`)
12. Create or edit one category: title, slug, parent, icon, image, description, per-locale translations, SEO fields.
3. **Not understandable at all today** — this form is close to 100% raw i18n keys: `adminCategories.title *`, `adminCategories.slug *`, `adminCategories.parent`, `adminCategories.icon`, `adminProducts.visible` (again, wrong namespace bleeding from Products into Categories), `adminCategories.image`, `adminCategories.description`, `adminProducts.translations`, three repeated blocks of `adminCategories.title ru/en/hy` + `adminCategories.description ru/en/hy`, `adminProducts.seo` / `.metaTitle` / `.metaDescription` / `.keywords`, `adminCategories.saveDraft` / `.publish`.
4. **Confusing (design-level, independent of the i18n bug):**
- **Slug field has no explanation.** A first-time non-technical user does not know what a "slug" is, why it's required (marked `*`), or that it drives the public URL.
- **Three full translation blocks (ru/en/hy) are shown inline, always, for every category**, even for a store that may only ever sell in one language. No progressive disclosure, no "same as default" shortcut, no indication of which language is required vs optional.
- Two save actions — "Save draft" and "Publish" — with no explanation of what "draft" means for a category (is it visible in the storefront tree while draft? Can customers browse it?).
5. **Missing:** No live slug preview (e.g. `yoursite.com/catalog/slug-here`), no character-count/length guidance on SEO meta fields, no validation-error summary, no "unsaved changes" warning surfaced in the UI (a dirty-guard exists in code but its user-facing message wasn't visible in this pass).
6. **Meaningless inputs:** SEO block (`metaTitle`, `metaDescription`, `keywords`) presented as three bare text inputs with zero preview of how they'll appear in a Google search result — this is the #1 place a preview snippet pays for itself.
7. **Technical labels:** "Slug" itself is the clearest example — should be "URL page address" or similar with the slug shown live, not a bare technical term as the primary label.
8. **Dangerous:** Not surfaced in create mode; needs a follow-up pass on the delete/archive flow from the list page specifically (confirm dialogs, soft-delete recovery path).
10. **Shopify equivalent (Collections):** single-language-first form with an explicit "Search engine listing preview" card, auto-slug from title with an "Edit" link (not a required raw field), and translations handled in a separate, optional side panel — not three duplicated blocks in the main form.
### 1.4 Products — List (`/backoffice/products`)
Same shape and same issues as Categories list (filters/search/create button all raw keys; "Products" empty state copy is one of the few translated strings that actually exists — `adminProducts.emptyTitle`/`emptyDescription` — showing the team did design *some* empty states, just not the majority of the surface).
### 1.5 Product editor — Create/Edit (`/backoffice/products/create`)
12. The most complex form in the admin: identity, media, pricing, inventory, content, per-locale translations, SEO, marketplace badges, variants, related products, and a live preview + read-only reviews/questions panel.
3. **Not understandable today** — effectively every label on this page is a raw key (55+ distinct keys rendered literally in this one pass alone).
4. **Confusing (design-level):**
- **One long unsegmented scroll** with no section navigation (no tabs, no sticky sub-nav, no "jump to Pricing/Inventory/SEO"). For a form this large, a first-time user has to scroll past media, pricing, inventory, content, three language blocks, SEO, and marketplace badges before reaching Save.
- **SKU is the only field shown untranslated as plain "SKU"** — every other field label is a raw key, but SKU renders correctly because it's literally the string `"SKU"` in the template, not a translation call. Accidental correctness, not intentional design — worth noting as a code smell alongside the i18n gap.
- **Stock status has three raw states** (`inStock`/`lowStock`/`outOfStock`) plus a separate `quantity` field and a separate `availability` field — three overlapping concepts (quantity, status, availability) with no visible explanation of how they relate to each other or which one wins if they disagree.
- **Badges section** (`featured`/`recommended`/`new`/`bestseller`) — four toggles with no explanation of where these badges actually appear to customers or what they do.
- **Variants section** has a `variantsHint` key specifically for this purpose (good instinct by whoever built it) but it too renders as a raw key, so the one field meant to reduce confusion currently adds to it.
5. **Missing:** No autosave/save-progress indicator on a form this long, no field-level validation feedback visible in this pass, no image alt-text field (accessibility gap — product images need alt text for screen-reader shoppers, not just an admin convenience).
6. **Meaningless inputs:** `Priority` field with no explanation of what it sorts or ranks. `Barcode` and `Brand` presented with equal visual weight to `Name`/`Price` — no visual hierarchy signaling which fields are essential vs optional for launch.
9. **Missing explanations:** SEO block, same gap as categories — no search-result preview.
10. **Shopify equivalent (Product editor):** clearly hierarchized sections with a persistent right-rail (status/organization/inventory) separate from a left-column content flow (title → media → description → pricing), inline "Preview" always visible, and every ambiguous field (SKU, barcode) has inline help text.
### 1.6 Orders — List (`/backoffice/orders`)
12. Browse all orders, filter by status, export.
3. Table structure itself (order #, customer, price, date, view) is legible even with raw status-key values, because the *layout* (a plain table) is a familiar pattern.
4. **Confusing:**
- Every status badge renders as `adminOrders.status.pending` / `.processing` / etc. instead of a real label — and there's no color-coding visible in the text-only pass to compensate (needs a live visual check for whether color alone carries the status, which would itself be an accessibility problem if true).
- Dates render as **`7/17/26, 11:39 PM`** — US month/day/year format — on a `ru` locale page. Confirmed locale-hardcoded date formatting bug, same family of issue as the previously-fixed `en/ru/hy` tab-hardcoding bug noted in [KNOWN-ISSUES.md](KNOWN-ISSUES.md).
- "24 `adminProducts.items`" — the pagination footer on the *Orders* list borrows the word "items" from the *Products* translation namespace. Third confirmed instance (after Categories' visibility filter and this) of admin modules cross-borrowing copy from Products instead of having their own strings — suggests Products was built first and every later module was scaffolded by copy-pasting it without renaming keys.
5. **Missing:** No bulk status update, no date-range filter (only a status filter is present), no order search by customer name/number.
8. **Dangerous:** "Export" action with no visible confirmation or indication of what leaves the system (all orders? filtered view? does it include customer PII, and if so is that flagged for the user?). Worth a data-privacy pass, not just a UX one.
10. **Shopify:** Orders list has saved filter views, bulk actions, and a search bar as first-class citizens, not an afterthought.
### 1.7 Order detail (`/backoffice/orders/:id`)
**This screen is broken, not just confusing.** Clicking "View" on any order in the list navigates to a detail page that shows **"Загрузка..." (Loading...) forever** — confirmed via network trace: no `/api/backoffice/orders/:id`-shaped request is ever fired, and the console logs two swallowed `HttpErrorResponse` errors with no resulting error state shown to the user. A first-time admin's very first attempt to inspect an order dead-ends on an infinite spinner with no error message, no retry button, and no way back except the browser Back button (there's no in-app back link visible on this screen in the current DOM). This is the most severe single finding in the audit — **flagging for engineering, not just design**, since it blocks the #1 reason an admin opens Orders at all.
### 1.8 Transactions (`/backoffice/transactions`)
12. Payment/refund ledger with fraud flags and retry/audit actions.
34. Same i18n gap as Orders, plus:
- **Payment method column shows raw backend enum values** — `card`, `qr`, `cash_on_delivery` — instead of humanized labels. `cash_on_delivery` in particular is a raw snake_case API value leaking straight into the UI.
- **Fraud flag column** shows `adminTransactions.flagged` for one row and is blank for the rest, with no legend explaining what "flagged" means, who flagged it, or why.
8. **Dangerous actions, under-protected:** `Flag`, `Clear flag`, `Retry`, `Audit` are all small inline row actions on financial transactions with no confirmation step visible and no explanation of consequence (does "Retry" re-charge the customer's card? Does "Clear flag" affect fraud reporting to a payment processor?). This is real financial admin surface and it currently reads like a debug table, not a finance tool.
10. **Shopify Payments equivalent:** every retry/refund action requires an explicit confirm step and states the exact monetary consequence before the click is committed.
### 1.9 Users & Permissions (`/backoffice/users`)
12. Manage admin team members, roles, invitations.
34. i18n gap again, plus:
- **Role permissions shown as raw dot-notation scope strings**: `"Admin — products.manage, categories.manage, orders.manage, media.manage"`, `"Viewer — products.view, orders.view"`. This is API/RBAC internal vocabulary shown directly to a business owner deciding who gets what access — genuinely unusable for the intended audience without translation into plain sentences ("Can edit products and categories, cannot manage users or see financials").
- `adminUsers.passwordlessHint` renders raw right next to the invite form — whatever it's meant to explain about the passwordless Telegram-based login flow is currently invisible.
8. **Dangerous actions, under-protected:** `Suspend` sits as a plain inline row action next to `Sessions` and `Audit` with equal visual weight — suspending a teammate's access should read as more consequential than viewing their session history, and today it doesn't.
10. **Shopify (Staff/permissions):** permission scopes are grouped into named presets with checkboxes and one-line plain-English descriptions per permission, never raw API scope strings.
### 1.10 Monitoring (`/backoffice/monitoring`)
1. **What is this page, really?** This is the single biggest audience-mismatch finding in the whole audit. The page shows **raw HTTP request logs** (`GET /api/products responded 200 in 84ms`), **queue depths** (`webhook-delivery`, `depth: 12`, `degraded`), and **webhook delivery status to partner endpoints** (`https://partner.example.com/orders`). This is an SRE/engineering observability dashboard, not a marketplace-owner feature.
2. **Goal:** unclear whether the intended audience is the store owner or the platform's own engineers.
3. **Understandable to a first-time non-technical user?** No — "queue depth", "webhook delivery", raw latency numbers, and stack-trace-style event messages ("Unhandled exception in checkout flow (#5)") are meaningless and mildly alarming to a merchant with no engineering background.
45. If this page is meant for merchants, it needs to be replaced with something like "system health at a glance" (payments working? notifications sending?) in plain language. If it's meant for engineers/support staff, it should be gated behind a separate internal-tools role, not sit as a peer nav item next to Products and Orders.
10. **Shopify** has no merchant-facing equivalent to this at all — this level of detail lives in an internal ops tool, never in the merchant admin.
### 1.11 Analytics (`/backoffice/analytics`)
12. Sales chart, top products, funnels/heatmaps, date-range toggle (7d/30d/90d).
34. Currently just renders `adminAnalytics.pendingBackend` / `.pendingBackendHint` — i.e. this whole section is an explicit "not built yet" placeholder. That's honestly fine *if* it's labeled as coming soon in real copy; today (raw keys) it looks like every other broken screen rather than an intentional placeholder, which is a missed opportunity — a well-designed "coming soon" state builds more trust than a silent one that looks identical to a bug.
10. **Shopify:** ships a real, if basic, analytics overview from day one (sessions, conversion rate, top products) — this is normally considered table-stakes, not a phase-2 feature, for a marketplace admin.
### 1.12 Media Library (`/backoffice/media`)
Best screen in the audit. Title ("Медиатека") and empty-state copy ("Пока нет файлов" / "Загрузите изображение или документ, чтобы начать") are actually translated and read like real product copy — this is proof the team can write good empty-state UX when the i18n step isn't skipped. Two folder-related labels (`mediaLibrary.allFolders`, `mediaLibrary.newFolder`) still leak raw, but the page overall is the clearest single evidence of the *intended* quality bar for the rest of the admin.
### 1.13 Admin login dialog
Attempting to reach any `/backoffice/**` route while unauthenticated shows a dialog titled **"Требуется авторизация"** (Authorization required) with body copy **"Для оформления заказа войдите через Telegram"** — *"To place an order, sign in via Telegram."* This is the **customer checkout login dialog's copy**, reused verbatim for the admin login gate. A store owner logging into their own admin panel is told they need to log in "to place an order," which is simply wrong context and actively confusing on the very first screen an admin ever sees.
---
## 2. Cross-cutting issues (apply to nearly every screen above)
| # | Issue | Where seen |
|---|---|---|
| A | No persistent admin navigation shell (sidebar/topbar/breadcrumbs) — every page is an island reachable only from the Dashboard's quick-action links | All screens |
| B | ~178 raw i18n keys across products/categories/orders/transactions/users/monitoring/analytics (tracked in KNOWN-ISSUES.md, Sprint 29) | Nearly all screens except Media Library |
| C | Copy-paste key bleed between modules (Products keys used in Categories/Orders/Transactions/Users tables and forms) — signals no shared, deliberately-named translation vocabulary for common table/action words ("items", "actions", "visible") | Categories, Orders, Transactions, Users |
| D | Raw backend/API vocabulary shown to end users (payment method enums, RBAC scope strings, "backend integration", "bootstrap", HTTP status/latency) | Dashboard, Transactions, Users, Monitoring |
| E | Locale-hardcoded date formatting (US format on `ru` pages) | Orders, Transactions, Monitoring |
| F | Dangerous/irreversible actions (suspend user, retry payment, clear fraud flag, export financial data) presented with the same visual weight as safe actions, no confirm step observed | Transactions, Users, Orders |
| G | No section navigation on long forms (product editor is one long scroll with 55+ fields) | Product editor, Category editor |
| H | No SEO/search-result preview despite collecting meta title/description/keywords | Product editor, Category editor |
| I | Ambiguous pass/fail status list with no per-item visual state | Dashboard system status |
| J | Audience mismatch — engineering-grade observability data presented as a merchant feature | Monitoring |
| K | Wrong-context dialog copy (checkout copy shown on admin login) | Login gate |
---
## 3. Redesign plan, prioritized by user impact
### P0 — Blocking (a first-time user cannot complete their core job without these)
| Task | Description | Why P0 |
|---|---|---|
| **R1. Build an admin shell** | Persistent sidebar or topbar with all 9 sections, active-state highlighting, and a clear "Admin" vs "Storefront" mode indicator (replace the reused public header entirely inside `/backoffice/**`, or clearly demarcate it). Add breadcrumbs on editor/detail pages. | Nothing else in this plan matters if users can't navigate between sections without returning to Dashboard each time. |
| **R2. Fix the Order detail infinite-loading bug** | Order detail must resolve to real data or a clear error/not-found state; never hang on "Loading..." forever. | Orders is a top-3 reason to open an admin panel at all; this is currently 100% broken. |
| **R3. Ship the missing i18n content** | Author the ~178 keys already scoped in KNOWN-ISSUES.md Sprint 29, and while doing so, de-duplicate/rename the cross-module key bleed (C above) into a proper shared vocabulary (`common.actions`, `common.itemsCount`, etc.) instead of copy-pasting Products' keys. | Every other design fix is invisible until real copy exists to evaluate it against. |
| **R4. Fix the admin login dialog copy** | Admin gate needs its own title/body copy ("Sign in to manage your store") distinct from the customer checkout dialog. | First screen every admin sees; currently tells them they're trying to place an order. |
### P1 — High impact (materially damages trust/usability but has workarounds)
| Task | Description |
|---|---|
| **R5. Humanize raw backend vocabulary** | Payment methods (`cash_on_delivery` → "Cash on delivery"), RBAC scopes (`products.manage, orders.manage` → "Can manage products and orders"), dashboard jargon ("backend integration", "bootstrap", "layout", "widgets") → plain merchant language with optional "advanced" tooltips for power users. |
| **R6. Redesign Dashboard stat cards and system-status list** | Distinguish "no data yet" from "failed to load" with different copy/iconography; give the system-status list explicit pass/fail icons per item, not a single "all passed" banner over mixed-sounding items; add a first-run onboarding checklist for a Draft-status store with 0 products. |
| **R7. Add confirm steps to dangerous actions** | Suspend user, retry payment, clear fraud flag, export data — each needs an explicit confirmation with a plain-language statement of consequence before it fires. |
| **R8. Fix locale-hardcoded date formatting** | Route all admin date rendering through the locale-aware formatter already used elsewhere in the app, not a hardcoded en-US format. |
| **R9. Reposition or gate Monitoring** | Decide the intended audience: if merchants, replace with a plain-language "system health" summary (payments OK / notifications OK); if internal engineering, move it out of the merchant-facing nav entirely and behind a separate internal role. |
### P2 — Meaningful polish (improves specific flows, not systemic)
| Task | Description |
|---|---|
| **R10. Segment the Product editor** | Sticky in-page section nav or tabs (Identity / Media / Pricing / Inventory / Content / Translations / SEO / Marketplace) instead of one long scroll; add a persistent Save/status rail. |
| **R11. Add SEO preview** | Live Google-style search-result preview wherever meta title/description/keywords are collected (products, categories). |
| **R12. Progressive disclosure for translations** | Default to the store's primary language; make additional-language fields collapsible/optional rather than always-expanded triplicate blocks. |
| **R13. Explain ambiguous fields inline** | Slug, Priority, Barcode, stock-status vs quantity vs availability, product badges (Featured/Recommended/New/Bestseller) — one-line inline help text or tooltip for each. |
| **R14. Turn Analytics' placeholder into a real "coming soon" state** | Once i18n exists, give it honest, designed placeholder copy and a rough timeline/CTA rather than a bare pending message indistinguishable from a bug. |
| **R15. Accessibility pass** | Product image alt-text field; verify status/fraud-flag badges don't rely on color alone (WCAG 1.4.1); verify all icon-only buttons (region selector, search) have accessible names — spot-checked via the accessibility tree during this audit but needs a dedicated pass with axe/AccessLint against real rendered pages. |
---
## 4. Suggested sequencing
R1R4 (P0) should land together or in immediate succession — R3 in particular unblocks a real visual/usability re-review of everything else, since today's raw-key state makes several judgments above (color-only status signaling, information density, visual hierarchy) impossible to fully confirm without live rendering. Recommend a follow-up visual (screenshot-based) pass once R3 ships, to validate color/contrast/hierarchy findings that this text-based audit could only flag as "needs live check."