Commit Graph

70 Commits

Author SHA1 Message Date
sdarbinyan
14467cc6fb feat: FX-quote-backed currency conversion, delete admin rate editor
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
F10-F12 of the frontend backlog. Contract: PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md §3.

Removed the failure mode §5 of that contract exists to close: rates were
typed once by an admin into Settings, persisted to localStorage, seeded from
a hardcoded DEFAULT_RATES table (USD: 0.011, AMD: 4.3) that never updated and
drifted from market. Nothing recorded which rate produced a displayed price
or when.

- currency-rates.service.ts   now fetches through FX_QUOTE_GATEWAY instead of
                              reading admin-typed/localStorage numbers. Stays
                              synchronous at the call site (getRate/convert) -
                              rewriting every consuming template to `| async`
                              is a separate, larger change (F13, not this
                              commit). Before a quote has loaded for a pair,
                              getRate returns 1 rather than a fabricated
                              market rate; isRateReady() lets a caller that
                              cares distinguish the two. ensureFreshQuote()
                              added for checkout to await before charging,
                              per contract §3.2's stale-quote policy.
- language.service.ts        setCurrency() now triggers a quote fetch instead
                              of just flipping the display signal.
- cart.component.ts           openPaymentPopup() awaits ensureFreshQuote()
                              before computing the charged amount.
- admin-settings-page.*        currency-rate editor deleted (F11) - card,
                              component state, and the three orphaned i18n
                              keys it was the only consumer of.

Two real bugs surfaced fixing this, neither cosmetic:

1. fx-quote-local.gateway.ts had CurrencyRatesService.convert() as its rate
   source. That is now circular - CurrencyRatesService depends on
   FX_QUOTE_GATEWAY, and under useMockData:true this gateway IS
   FX_QUOTE_GATEWAY. Would have recursed the moment mock FX data was
   exercised. Fixed by giving the local gateway its own static mock table -
   the correct home for those numbers now: explicitly labelled dev/mock data,
   only wired in behind useMockData, never presented as a live rate.

2. currency-convert.pipe.ts memoized its result on (amount, from, to) alone.
   That was already latently wrong - rates could change via the old
   setRate() without the pipe re-evaluating for an already-rendered price -
   but never surfaced because rates never changed mid-session in practice.
   Async quote loading made it concrete and reproducible: a price rendered
   before its quote arrived stayed wrong forever, because none of the three
   cached inputs ever changed again on their own. Fixed with a ratesVersion
   counter on the service, bumped on every quote arrival, included in the
   pipe's cache key.

Both found and fixed via the E2E suite (docs from the prior commit) actually
exercising the real code path: GET /api/v2/pricing/fx-quote intercepted with
a contract-shaped response rather than flipping the whole app into mock mode,
so the test runs the real FxQuoteApiGateway, not a stand-in for it.

Verified: 3/3 E2E green, 115/115 unit tests green, arch:check clean,
production build succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 14:03:02 +04:00
sdarbinyan
14c72d1a6a feat: extract auth into @marketplaces/auth package, add backoffice admin provisioning spec
- ADR-0001: decision to extract auth/payment into shared @marketplaces/* packages
- Scaffold packages/auth, packages/payment; @marketplaces/auth now holds the real
  telegram (customer+admin QR/session) and ed25519 (future admin challenge/response)
  auth implementation, pushed to sources.vitanova.network/sdarbinyan/vitanovaPackages
- Rewire ~30 call sites to import from @marketplaces/auth; delete migrated originals
  from core/auth, core/admin-auth, services/, models/
- Replace environment coupling with AUTH_API_URL/TELEGRAM_BOT_USERNAME injection
  tokens and isDevMode(); wired as file:packages/auth pending registry publish
- Add TRACK-S §8: bootstrap per-marketplace admin login + marketplace-scoped
  sub-admin invite/role endpoints
- Build, arch:check:boundaries, and full test suite (103/103) all green

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 01:05:16 +04:00
sdarbinyan
ffaa6d2a1c refactor: rename storefront CategoryApiModel; correct stale auth-error doc; add Phase 1 backend contract
- models/category.model.ts: Category -> CategoryApiModel, disambiguated
  from core/categories/models/category-domain.model.ts's Category (admin
  domain shape). Removes a dead unused import in item.utils.ts along the
  way. Only live consumer was services/api.service.ts, updated in place.
- BACKEND-API-REFERENCE.md §5: corrected two rows documenting the
  TOKEN_EXPIRED/INVALID_SIGNATURE auth-error bug as still open - the fix
  (reading error.error.code before falling back to HTTP status) is
  already in auth.service.ts. Doc was stale, not the code.
- Sprint 0.2 audit: AdminRole duplication and the
  PRODUCT_DATA_PROVIDER/CATEGORY_REPOSITORY dead mock branches were
  already resolved in a prior pass - verified, no code change needed.
- docs/backend/PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md: new wire contract
  for Money/FxQuote/PriceSnapshot/payment state machine, so backend can
  start Phase 1 the moment the frozen payment chain is unblocked.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:31:11 +04:00
sdarbinyan
4510eb769a feat: client-side currency conversion with admin-configurable rates
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
- CurrencyRatesService: RUB-based rates, persisted via localStorage
- CurrencyConvertPipe: impure pipe converting item price to selected currency
- Admin settings page: editable currency rates form
- Applied conversion to product-card, product-information, quick-view-dialog,
  delivery-information, compare-table, cart totals + payment payload

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 01:45:12 +04:00
sdarbinyan
d8c078ad5a fix: review findings from full-diff audit (7 fixed)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
- popularSearches sent translated display text as the actual search
  query instead of the canonical term - useSuggestion() now prefers
  target.query.q when present.
- CartService.addItem() dedup guard resolved immediately instead of
  awaiting the real in-flight add; now tracks the pending Promise per
  itemID so concurrent callers await the actual result.
- addItem()'s Promise never rejected on failure (resolve() in both
  next/error branches) - now rejects on error; buyNow() catches and
  shows an error toast instead of navigating on a failed add.
- Quick View had no stale-response guard - a slower earlier request
  could overwrite a faster later one. Added a request-generation
  counter.
- cart autoSubmitPurchase() set paymentStatus to null synchronously
  right after firing the async submit call, blanking the success
  screen while the request was still in flight. Removed the
  redundant/harmful line.
- Order terminal-status guard (cancelled/refunded can't be reopened)
  lived only in the page component. Moved enforcement into the
  gateway (single write path) via a shared TERMINAL_ORDER_STATUSES
  const, so no future caller can bypass it.
- TranslatePipe's per-instance memoization cache had no eviction,
  so bindings with volatile params (pagination counts) grew it
  unbounded for the component's lifetime. Capped at 50 entries.

Not changed: the dark-mode color override was flagged as clobbering
admin branding, but it's the exact palette explicitly requested this
session for the global dark default - not a bug.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 17:20:00 +04:00
sdarbinyan
0b08802996 feat: real back-in-stock subscription for Notify Me (API + localStorage fallback)
notifyMe() just called toggleWishlist() - no actual subscription
mechanism existed. Now calls a new subscribeToRestock() API method
(POST /items/{id}/notify-me, not yet built server-side - see
BACKEND-API-REFERENCE.md §12.5) and falls back to a local-only record
in localStorage on failure, so the request isn't silently dropped
while the backend catches up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:30:53 +04:00
sdarbinyan
3feb806caa feat: add JSON-LD structured data for product and site pages
JSON-LD was absent entirely (sitemap generation is separate backend
work, out of scope here). Added Product schema (name/description/
image/offers with price+availability) on item pages via setItemMeta(),
and a site-wide Organization schema via resetToDefaults(), both
injected as a single #seo-json-ld <script type=application/ld+json>
tag that gets replaced on navigation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:16:37 +04:00
sdarbinyan
c0bce7feac fix: Buy Now raced ahead of addToCart, per-product SEO tags never applied
CartService.addItem() fired an async dynamic import + API call for
new items but returned void immediately - buyNow() navigated to /cart
before the item was actually added, landing the user on an empty or
stale cart. addItem() now returns a Promise that resolves once the
cart signal actually contains the item; buyNow() awaits it before
navigating.

Also wired SeoService.setItemMeta()/resetToDefaults() into the product
detail page - built and working, but never called anywhere, so every
product page rendered the site-wide default OG/Twitter tags instead of
per-product ones.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:06:46 +04:00
sdarbinyan
9f784406d7 fix: og:locale hardcoded ru_RU regardless of active locale
Both setItemMeta() and resetToDefaults() hardcoded 'ru_RU'. Added a
LanguageService-driven mapping (ru/en/hy -> ru_RU/en_US/hy_AM).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:02:18 +04:00
sdarbinyan
4ef5ea2f58 fix: cart payment/order currency ignored the selected currency
createPayment() and recordOrder() hardcoded currency: 'RUB' regardless
of LanguageService.currentCurrency() (app supports RUB/USD/EUR/AMD).
Widened CartPaymentRequest.currency from a 'RUB' literal to string and
use the actual selected currency in both calls.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 07:39:35 +04:00
sdarbinyan
9fa3321322 fix: stop sending client-computed price on order creation
createOrder() sent a discount-applied price per line item that the
client computed itself, with no server revalidation. Items now only
carry productId/name/quantity - the backend must price from its own
catalog. createPayment()'s amount (required to actually charge the
payment gateway) is unchanged; backend must revalidate it instead,
tracked in BACKEND-API-REFERENCE.md §12.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 07:24:08 +04:00
sdarbinyan
6cd61fc873 chore(cleanup): Phase 1 - remove dead code, fix orphaned SeoService wiring
Ran knip to find unused exports/dependencies (deps already clean, no
unused packages/files found).

Removed genuinely dead code (verified zero references anywhere,
including templates):
- 4 unused constants in config/constants.ts (scroll/pagination/search
  thresholds never consumed)
- isAdminRole(), toSearchResult(), createInitialSearchState(),
  getTranslatedCategoryName() - unused utility functions
- DEFAULT_EDITOR_HEADER_CONFIG - unused constant
- TelegramService - entire file deleted; cart.component.ts/
  cart.service.ts already implement the same window.Telegram.WebApp
  access directly, this was an unused duplicate

Real bug fix found during the sweep: SeoService has providedIn:'root'
with a live effect() meant to sync <title>/OG/canonical tags to
tenant bootstrap config, but nothing in the app ever injected it, so
Angular never instantiated it and the effect never ran - the SEO sync
a prior sprint reported as "done and verified" was actually dead on
arrival. Fixed by injecting SeoService in the root App component.

Left alone: ~125 knip-flagged "unused exported types" - overwhelmingly
config/schema interfaces for the widget/theme/admin domain models,
high false-positive rate for this kind of interface-heavy Angular app,
deleting blind risks breaking structural type contracts. Also left
locally-used-but-over-exported helpers (toCssColor/toBackendColor,
HTML_EDITOR_TOOLBAR*, HISTORY_LIMIT, DEFAULT_CATALOG_PAGE_SIZE) - real
code, not dead, just exported wider than needed.

tsc --noEmit and ng build --configuration=production both clean (only
pre-existing bundle-budget warning, unrelated).

Files changed: src/app/app.ts, src/app/config/constants.ts,
src/app/core/auth/models/permission.model.ts,
src/app/core/products/models/catalog-experience.model.ts,
src/app/core/search/models/search-state.model.ts,
src/app/features/project-editor/models/project-editor.model.ts,
src/app/services/index.ts, src/app/utils/item.utils.ts,
src/app/services/telegram.service.ts (deleted)
2026-07-26 15:39:54 +04:00
sdarbinyan
f261800159 feat(payment): add qrDescription/customerID fields, TTL-based QR polling
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
- CartPaymentRequest gains qrDescription (brandName > hostname > fallback
  text) and customerID (telegram id)
- QrCreateResponse gains qrTTL; polling window now derived from it
  (min 60s) instead of a fixed 3-minute/36-check cap
- PAYMENT_MAX_CHECKS replaced by PAYMENT_MIN_POLL_SECONDS
2026-07-23 00:26:35 +04:00
sdarbinyan
fd5a436220 api doc
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
2026-07-20 01:02:36 +04:00
sdarbinyan
3839e2e1f6 fix(seo): wire branding.socialImageUrl into the OG/Twitter image fallback
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Added in an earlier commit this session (branding OG image + gallery
field), but SeoService.resetToDefaults() never actually read it -
defaultImage fell back straight to appIconUrl/logoUrl, so the field the
editor calls 'Social Share Image' had no runtime effect. Now it's
checked first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-17 18:19:33 +04:00
sdarbinyan
173ceb8081 feat(seo): tenant-driven meta tags, sitemap/robots, reduced-motion, docs
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Completes the rest of Sprint 28 on top of the earlier admin-scoped
a11y/skeleton pass (576f260):

- SeoService.resetToDefaults() now reads real bootstrap.seo.default /
  branding instead of hardcoded placeholder text + a broken
  /og-image.jpg reference; auto-reapplies via an effect() whenever
  bootstrap (re)loads, same pattern as UiRuntimeFacade.
- New public/sitemap.xml (static baseline, documented per-tenant-dynamic
  limitation) + public/robots.txt Sitemap directive and admin/editor
  Disallow rules.
- Global prefers-reduced-motion override in styles.scss covering every
  existing hover-transform/fade-in/shimmer animation in one place.
- New adminProducts/adminUsers/adminMonitoring/adminAnalytics
  empty-state i18n keys (en/ru/hy) for this sprint's skeleton/empty-state
  consistency fixes.
- docs/KNOWN-ISSUES.md: logged a newly-found, much larger pre-existing
  gap (~178 missing adminXxx.* i18n keys across the whole admin
  backoffice) - deferred to Sprint 29's translation validation, not
  fixed here.
- docs/BACKEND.md: new item 17 (sitemap generation gap).
- docs/ADMIN.md, docs/SPRINT-PLAN.md: rewritten Sprint 28 sections to
  describe the full, combined scope (both commits) instead of the
  earlier admin-only framing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 00:19:41 +04:00
sdarbinyan
3cf732797c refactor: route localStorage access through shared LocalStorageService
Sprint 2 high-priority cleanup: cart/language/location services called
localStorage directly, bypassing the try/catch safety and core/<domain>
pattern used elsewhere (e.g. ProjectEditorDraftStorageService). New
core/storage/LocalStorageService centralizes get/set/remove and JSON
helpers with private-mode/quota error handling, reused across all three.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 02:58:39 +04:00
sdarbinyan
6aec2ebcb2 fix(admin-auth): reuse exact same QR/session API and component for admin login
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
- Removed invented adminAuthApiUrl endpoint and separate AdminLoginComponent.
  Admin login now uses the exact same Telegram session backend
  (TelegramSessionApiService, {authApiUrl}/users/sessions) and the exact
  same TelegramLoginComponent (mode="customer" | "admin" input) as customer
  login - only the storage (cookie/localStorage/signals) stays separate.
- Extracted the shared HTTP+normalization logic from AuthService into
  TelegramSessionApiService so both AuthService and AdminAuthService call it
  instead of duplicating request/parsing code.
- Documented the resulting backend gap in docs/Project-Editor.md: since the
  session API has no concept of "admin", server-side role enforcement is
  required when admin API calls are made - the frontend only decides where
  to store the session, not whether the user is actually an admin.
2026-07-14 10:13:59 +04:00
sdarbinyan
3877b70fdf feat(sprint18): editor autosave/reset, admin auth, QR reuse, Ed25519 prep
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
- 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
sdarbinyan
aed0a47388 feat(product): add reusable Product Experience 2.0
Unify product details modules behind config-driven contracts so teams can
extend UX without changing runtime architecture or bootstrap flow.

Keep backward compatibility with existing product payloads by treating new
media/specification/variant/related structures as optional extensions.

Improve conversion and content discoverability with reusable actions,
typed media rendering, grouped specifications, dynamic variants, and
multi-collection related products.
2026-07-10 13:10:35 +04:00
sdarbinyan
6550250d13 cleanup: remove tenant variant logic and enforce config-driven UI 2026-07-05 04:07:25 +04:00
sdarbinyan
487a3fb913 Sprint 9: add tenant-driven API resolution layer 2026-07-05 02:24:16 +04:00
sdarbinyan
01d2b26021 Support variant-aware cart lines 2026-07-05 01:12:07 +04:00
sdarbinyan
58b5a4e996 clean up stage 1 2026-07-05 00:38:21 +04:00
sdarbinyan
960901d2b2 polling 2026-06-29 22:15:19 +04:00
sdarbinyan
0977f302a4 changes 2026-06-29 00:06:18 +04:00
sdarbinyan
3cf0ef87f8 bank type added 2026-06-28 22:18:35 +04:00
sdarbinyan
1190969d67 array 2026-06-22 10:46:51 +04:00
sdarbinyan
394ac5ec9d visible and count 2026-06-22 01:45:23 +04:00
sdarbinyan
4fb918f5e4 cleaned up 2026-06-21 23:42:39 +04:00
sdarbinyan
3b802b7c7b delivery 2026-06-21 23:13:01 +04:00
sdarbinyan
6410321895 price 2026-06-20 15:16:25 +04:00
sdarbinyan
04814aeeda reset 2026-06-20 14:40:22 +04:00
sdarbinyan
9aaff4d80a removed parazite 2026-06-19 16:13:54 +04:00
sdarbinyan
7a06843bf5 fixes 2026-06-19 15:01:54 +04:00
sdarbinyan
1decc08f77 userId 2026-06-19 12:43:25 +04:00
sdarbinyan
bdc330c885 chagned status 2026-06-18 13:11:05 +04:00
sdarbinyan
80cc90d347 api changes 2026-06-06 22:38:01 +04:00
sdarbinyan
9b5c2dd95c api 2026-06-06 19:25:00 +04:00
sdarbinyan
58e0869916 api changed 2026-06-06 16:16:37 +04:00
sdarbinyan
14bdd3bcd0 api change 2026-06-05 18:23:24 +04:00
sdarbinyan
e53c8230e6 payment 2026-06-02 02:12:08 +04:00
sdarbinyan
c6bc05560e change 2026-06-02 01:46:12 +04:00
sdarbinyan
63b0e18396 api change 2026-06-02 00:57:36 +04:00
sdarbinyan
4d8dc6b59c api auth 2026-06-01 00:47:26 +04:00
sdarbinyan
cb2666177a lavero 2026-05-19 02:01:36 +04:00
sdarbinyan
6e5fb3b86a QR login 2026-04-14 23:14:26 +04:00
sdarbinyan
db781fd871 qr login with telegram 2026-03-25 15:32:50 +04:00
sdarbinyan
64288b5ce1 offer 2026-03-25 14:27:53 +04:00
sdarbinyan
ee23fd2d3c color 2026-03-24 03:12:04 +04:00