Files
marketplaces/docs/Project-Editor.md
sdarbinyan 3877b70fdf
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
feat(sprint18): editor autosave/reset, admin auth, QR reuse, Ed25519 prep
- Project editor: persist draft to localStorage, restore on reload,
  last-saved/draft-restored status indicators, section/whole-draft reset
  with confirmation.
- Extract shared QR/polling/expiry engine from TelegramLoginComponent
  (shared/qr-login) and reuse it for a new admin login flow.
- Admin authentication kept fully separate from customer session:
  own cookie/localStorage keys, signals, guard, and header interceptor
  (core/admin-auth).
- ?login=true / ?adminLogin=true open the respective login dialog for
  manual testing.
- Ed25519 challenge/verify interfaces (fail-closed no-op binding) ready
  for backend delivery.
- Document autosave/reset/admin-auth/QR-reuse/Ed25519 model and the
  remaining full-field-coverage gap in docs/Project-Editor.md.
2026-07-14 09:50:03 +04:00

11 KiB

Marketplace Project Editor - Sprint 13 (updated Sprint 18)

Scope

Sprint 13 introduces first version of Marketplace Project Editor.

Editor only manages bootstrap configuration.

Out of scope:

  • products
  • categories
  • orders
  • analytics management

Architecture

src/app/features/project-editor/
  pages/
  components/
  sections/
  models/
  services/
  facade/

Editor uses strongly typed bootstrap models and isolated feature state. Components do not call APIs directly.

Facade API

ProjectEditorFacade exposes:

  • loadBootstrap()
  • updateBootstrap()
  • exportBootstrap()
  • importBootstrap()
  • preview()

Current source of truth:

  • existing bootstrap provider via ConfigService

Future backend integration:

  • replace local import/export/save flow with builder endpoints
  • keep component contracts unchanged

Supported Sections

  • General
    • marketplace name
    • domain
    • description
    • default language
    • supported languages
  • Branding
    • logo
    • small logo
    • favicon
    • marketplace title
  • Theme
    • palette tokens via color pickers
  • Header
    • logo/search/categories/languages/cart/profile/wishlist/compare/region toggles
  • Footer
    • company information
    • address
    • phone
    • email
    • copyright
    • payment icons
    • social links
    • static pages
  • Homepage
    • home page sections list
    • visibility
    • order
    • layout
    • drag-and-drop ordering
  • Widgets
    • homepage widget configuration
    • typed shortcuts for hero/categories/product collection widgets
    • JSON fallback for other widget props
  • Marketplace Features
    • feature flags
    • search suggestions/history
    • recently viewed
    • reviews/questions/recommendations
  • Preview
    • export JSON
    • import JSON
    • runtime preview without full browser refresh
  • Languages (Sprint 16)
    • add/remove supported locale
    • set default locale
    • generically syncs translation keys across static pages and navigation labels (LocaleSyncService)
  • Navigation (Sprint 16)
    • header navigation: add/remove/reorder/edit label/URL/visibility
    • flat footer navigation: same actions
    • grouped footer navigation (column-based) is read-only in this tab for now

Preview Strategy

Preview updates bootstrap snapshot in memory and re-applies:

  • theme
  • branding
  • runtime state
  • route-based page rendering on next Angular navigation

This keeps bootstrap engine intact while enabling fast local preview.

Widget Configuration

Current widget editor supports explicit fields for:

  • Hero
    • layout
    • height
    • overlay
    • autoplay
  • Categories
    • layout
    • columns
  • Product Collection
    • layout
    • cards per row
    • filters
    • badges
    • rating
    • price

Other widgets use JSON props fallback until dedicated editors are added.

Draft / Publish (Sprint 16, autosave added Sprint 18)

There is still no backend draft/publish API. This sprint models it client-side in ProjectEditorFacade:

  • status: 'draft' | 'published' and dirty (diffed against the last-saved snapshot) live in facade state.
  • save() snapshots the current in-memory bootstrap as "last saved" and timestamps it (lastSavedAt).
  • publish() runs ProjectValidator, and if there are no issues, applies the bootstrap via PlatformRuntimeService.reloadFromBootstrap, marks status published, and becomes the new originalBootstrap snapshot used by reset.

Backend gap, not yet implemented: real persistence needs PUT /builder/bootstrap/draft and POST /builder/bootstrap/publish endpoints so drafts/publishes survive a reload and are shared across editors.

Autosave (Sprint 18)

ProjectEditorDraftStorageService (services/project-editor-draft-storage.service.ts) persists the full bootstrap draft to localStorage (key projectEditor.draftBootstrap.v1, scoped by tenant.id) on every updateBootstrap() call, save(), and publish(). On loadBootstrap(), if a stored draft exists for the same tenant it is loaded instead of the freshly-fetched bootstrap and draftRestored is set true (surfaced in the save bar as a dismissible notice). The published/loaded bootstrap is never overwritten automatically — only explicit publish() calls change what the runtime actually serves; the localStorage draft is a separate, purely local concern that survives refreshes and browser restarts.

Status indicators in ProjectEditorSaveBarComponent:

  • Unsaved changes - shown while dirty() is true.
  • Last saved: HH:MM:SS - shown once not dirty and lastSavedAt is set.
  • Draft restored banner - shown once after a local draft is loaded from a previous session, dismissible.

Reset (Sprint 18)

  • Reset section - button above the active section (shown only for sections with a bootstrap-key mapping in EDITOR_SECTION_BOOTSTRAP_KEYS, models/project-editor.model.ts). Reverts that section's bootstrap keys to originalBootstrap (the last loaded/published snapshot). Confirmation required.
  • Reset draft - button in the save bar. Reverts the entire bootstrap to originalBootstrap and clears the persisted local draft. Confirmation required.
  • Per-field reset is not implemented - the bootstrap schema has no registry of per-field defaults, so only section- and project-level reset exist. Adding field-level reset would require either a default-value registry per field or storing per-field undo history; deferred.

Validation

ProjectValidator (services/project-validator.service.ts) runs on every render of the save bar: missing logo, no languages, invalid marketplace URL, duplicate static-page slugs, empty homepage, a homepage widget with no type, duplicate header navigation links, invalid theme colors. Publish is blocked while any issue is present; Save is not.

Rich HTML editing

Static page HTML is edited via MarketplaceHtmlEditorComponent (components/html-editor/), a contentEditable + toolbar component with no external dependency. It emits raw HTML on every change and never sanitizes — sanitization remains a storefront-render concern.

QR Login Reuse (Sprint 18)

The Telegram QR-login flow (QR image, polling, expiry, "return from app" recovery via visibilitychange/focus/pageshow) was extracted from TelegramLoginComponent into shared/qr-login/qr-login.engine.ts (QrLoginEngine<TSession>) plus an adapter interface (shared/qr-login/qr-login.model.ts, QrLoginAdapter<TSession>). The engine is not a DI singleton - each login surface instantiates its own new QrLoginEngine(adapter) and drives it from an effect() watching its own dialog-visibility signal. TelegramLoginComponent was refactored onto this engine with no behavior change. AdminLoginComponent (core/admin-auth/admin-login.component.ts) reuses the same engine against a separate adapter backed by AdminAuthService, so QR/polling/timeout logic is not duplicated between customer and admin login.

Admin Authentication (Sprint 18)

Admin authentication is completely separate from the customer/storefront session (AuthService), by design - one must never authenticate the other:

Customer (AuthService) Admin (AdminAuthService, core/admin-auth/)
Cookie webSessionID adminSessionID (SameSite=Strict)
Anonymous/local id web_session_id (localStorage, API attribution only) adminToken / adminRefreshToken (localStorage, reserved for future JWT pair)
Signals session, status, showLoginDialog on AuthService session, status, showLoginDialog, role on AdminAuthService
Guard none yet for customer routes adminAuthGuard (core/admin-auth/admin-auth.guard.ts)
Interceptor apiHeadersInterceptor adminAuthHeadersInterceptor (core/admin-auth/admin-auth-headers.interceptor.ts), self-guards on /admin/ in the URL, sets AdminWebSessionID + Authorization: Bearer <adminToken> when present
Login UI TelegramLoginComponent (mounted per-page, e.g. cart) AdminLoginComponent (mounted once, globally, in app.html)

Backend gap: environment.adminAuthApiUrl (https://users.vitanova.network:456/admin) is a placeholder path under the existing auth host - there is no real admin session/login backend yet. AdminAuthService.createWebSession() / checkSessionOnce() / logout() call POST|GET|DELETE {adminAuthApiUrl}/sessions... following the same shape as the customer session API; confirm/repoint this once the backend ships dedicated admin endpoints.

Login test mode

?login=true and ?adminLogin=true query params (handled once in App.ngOnInit via openLoginDialogsFromTestModeQueryParams(), app.ts) call AuthService.requestLogin() / AdminAuthService.requestLogin() respectively, for manual testing. This only sets the same signal a normal "please log in" action would set - it does not bypass authentication or change any other behavior, so it is safe in all environments. Note TelegramLoginComponent is currently mounted only on the cart page, so ?login=true only shows a dialog there; AdminLoginComponent is mounted globally so ?adminLogin=true works from any route.

Ed25519 prep

core/admin-auth/ed25519-verification.model.ts defines Ed25519VerificationService (abstract, injectable) with requestChallenge() / verify(signedResponse) and the Ed25519Challenge / Ed25519SignedResponse / Ed25519VerificationResult shapes (nonce, timestamp, payload, public key, signature). No crypto is implemented. The current DI binding, NoopEd25519VerificationService (registered in app.config.ts), fails closed (throws) rather than silently accepting anything, so it's safe to wire into a real login path today - it will error loudly instead of pretending to verify a signature. Swap the DI binding for a real implementation once the backend ships challenge/verify endpoints; nothing else needs to change.

Known gaps / deferred (Sprint 18)

Full field-by-field coverage of every supported bootstrap property (with bilingual EN/RU labels, description, and validation state per field) was not completed in this pass - the bootstrap schema is large (theme typography/ spacing/shadows, full company address, per-locale footer copyright, grouped footer navigation editing, sidebar navigation, per-page SEO map, catalog/ product-page/user-experience sub-fields, permissions, API endpoints) and several concepts named in the sprint brief (payments, delivery/shipping, checkout, unified search config) have no corresponding model in shared/models/config at all - they would need new bootstrap schema before an editor could expose them. See the section-by-section gap list gathered during Sprint 18 investigation for the full inventory; treat as a follow-up sprint rather than something silently skipped.

Constraints

  • runtime bootstrap engine not replaced
  • configuration stays source of truth
  • no hardcoded marketplace values
  • no business domain management mixed into editor