From a670ca994f59812acdcce96969360ea008ff8ce5 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Sat, 25 Jul 2026 23:56:13 +0400 Subject: [PATCH] chore(cleanup): delete unrouted legacy pages, update docs pages/category, pages/search, pages/item-detail, pages/info/**, pages/legal/** (40+ files) were entirely unrouted dead code: - category/:id, category/:id/items, search all redirect/route to CatalogContainerComponent - product/:id routes to ProductDetailsContainerComponent, not item-detail - cmsContentRoutes (meant to route info/legal) is a literal empty array; static/legal content is served by the CMS-driven :staticPath -> StaticPageComponent route instead dynamic-renderer/ is unrelated and stays - confirmed active, it's the live homepage rendering pipeline (HomeComponent -> WebsiteRuntimeFacade -> PageRendererService/PageResolverService -> DynamicPageLayoutComponent). Updated docs/TODO.md, docs/KNOWN-ISSUES.md, docs/FRONTEND-ROADMAP.md, docs/PROJECT_INDEX.md to reflect the resolution. --- docs/FRONTEND-ROADMAP.md | 6 +- docs/KNOWN-ISSUES.md | 23 +- docs/PROJECT_INDEX.md | 4 - docs/SPRINTS.md | 235 ++ docs/TODO.md | 2 +- .../pages/category/category.component.html | 63 - .../pages/category/category.component.scss | 391 ---- src/app/pages/category/category.component.ts | 127 -- .../category/subcategories.component.html | 102 - .../category/subcategories.component.scss | 491 ---- .../pages/category/subcategories.component.ts | 138 -- src/app/pages/info/about/about.component.html | 5 - src/app/pages/info/about/about.component.scss | 2 - src/app/pages/info/about/about.component.ts | 16 - .../info/about/en/about-en.component.html | 77 - .../pages/info/about/en/about-en.component.ts | 9 - .../info/about/hy/about-hy.component.html | 76 - .../pages/info/about/hy/about-hy.component.ts | 9 - .../info/about/ru/about-ru.component.html | 77 - .../pages/info/about/ru/about-ru.component.ts | 9 - .../info/contacts/contacts.component.html | 5 - .../info/contacts/contacts.component.scss | 46 - .../pages/info/contacts/contacts.component.ts | 16 - .../contacts/en/contacts-en.component.html | 49 - .../info/contacts/en/contacts-en.component.ts | 9 - .../contacts/hy/contacts-hy.component.html | 48 - .../info/contacts/hy/contacts-hy.component.ts | 9 - .../contacts/ru/contacts-ru.component.html | 49 - .../info/contacts/ru/contacts-ru.component.ts | 9 - .../info/delivery/delivery.component.html | 5 - .../info/delivery/delivery.component.scss | 2 - .../pages/info/delivery/delivery.component.ts | 16 - .../delivery/en/delivery-en.component.html | 60 - .../info/delivery/en/delivery-en.component.ts | 9 - .../delivery/hy/delivery-hy.component.html | 60 - .../info/delivery/hy/delivery-hy.component.ts | 9 - .../delivery/ru/delivery-ru.component.html | 60 - .../info/delivery/ru/delivery-ru.component.ts | 9 - .../pages/info/faq/en/faq-en.component.html | 304 --- src/app/pages/info/faq/en/faq-en.component.ts | 12 - src/app/pages/info/faq/faq.component.html | 5 - src/app/pages/info/faq/faq.component.scss | 62 - src/app/pages/info/faq/faq.component.ts | 16 - .../pages/info/faq/hy/faq-hy.component.html | 304 --- src/app/pages/info/faq/hy/faq-hy.component.ts | 12 - .../pages/info/faq/ru/faq-ru.component.html | 304 --- src/app/pages/info/faq/ru/faq-ru.component.ts | 12 - .../guarantee/en/guarantee-en.component.html | 158 -- .../guarantee/en/guarantee-en.component.ts | 9 - .../info/guarantee/guarantee.component.html | 5 - .../info/guarantee/guarantee.component.scss | 2 - .../info/guarantee/guarantee.component.ts | 16 - .../guarantee/hy/guarantee-hy.component.html | 158 -- .../guarantee/hy/guarantee-hy.component.ts | 9 - .../guarantee/ru/guarantee-ru.component.html | 158 -- .../guarantee/ru/guarantee-ru.component.ts | 9 - .../item-detail/item-detail.component.html | 344 --- .../item-detail/item-detail.component.scss | 2019 ----------------- .../item-detail/item-detail.component.ts | 369 --- .../company-details.component.html | 5 - .../company-details.component.scss | 95 - .../company-details.component.ts | 16 - .../en/company-details-en.component.html | 106 - .../en/company-details-en.component.ts | 9 - .../hy/company-details-hy.component.html | 104 - .../hy/company-details-hy.component.ts | 9 - .../ru/company-details-ru.component.html | 106 - .../ru/company-details-ru.component.ts | 9 - .../en/payment-terms-en.component.html | 117 - .../en/payment-terms-en.component.ts | 12 - .../hy/payment-terms-hy.component.html | 117 - .../hy/payment-terms-hy.component.ts | 12 - .../payment-terms.component.html | 6 - .../payment-terms.component.scss | 2 - .../payment-terms/payment-terms.component.ts | 16 - .../ru/payment-terms-ru.component.html | 117 - .../ru/payment-terms-ru.component.ts | 12 - .../en/privacy-policy-en.component.html | 367 --- .../en/privacy-policy-en.component.ts | 9 - .../hy/privacy-policy-hy.component.html | 367 --- .../hy/privacy-policy-hy.component.ts | 9 - .../privacy-policy.component.html | 5 - .../privacy-policy.component.scss | 1 - .../privacy-policy.component.ts | 16 - .../ru/privacy-policy-ru.component.html | 367 --- .../ru/privacy-policy-ru.component.ts | 9 - .../en/public-offer-en.component.html | 529 ----- .../en/public-offer-en.component.ts | 12 - .../hy/public-offer-hy.component.html | 529 ----- .../hy/public-offer-hy.component.ts | 12 - .../public-offer/public-offer.component.html | 5 - .../public-offer/public-offer.component.scss | 2 - .../public-offer/public-offer.component.ts | 16 - .../ru/public-offer-ru.component.html | 529 ----- .../ru/public-offer-ru.component.ts | 12 - .../en/return-policy-en.component.html | 134 -- .../en/return-policy-en.component.ts | 9 - .../hy/return-policy-hy.component.html | 134 -- .../hy/return-policy-hy.component.ts | 9 - .../return-policy.component.html | 5 - .../return-policy.component.scss | 2 - .../return-policy/return-policy.component.ts | 16 - .../ru/return-policy-ru.component.html | 134 -- .../ru/return-policy-ru.component.ts | 9 - src/app/pages/search/search.component.html | 105 - src/app/pages/search/search.component.scss | 394 ---- src/app/pages/search/search.component.ts | 175 -- 107 files changed, 252 insertions(+), 11159 deletions(-) create mode 100644 docs/SPRINTS.md delete mode 100644 src/app/pages/category/category.component.html delete mode 100644 src/app/pages/category/category.component.scss delete mode 100644 src/app/pages/category/category.component.ts delete mode 100644 src/app/pages/category/subcategories.component.html delete mode 100644 src/app/pages/category/subcategories.component.scss delete mode 100644 src/app/pages/category/subcategories.component.ts delete mode 100644 src/app/pages/info/about/about.component.html delete mode 100644 src/app/pages/info/about/about.component.scss delete mode 100644 src/app/pages/info/about/about.component.ts delete mode 100644 src/app/pages/info/about/en/about-en.component.html delete mode 100644 src/app/pages/info/about/en/about-en.component.ts delete mode 100644 src/app/pages/info/about/hy/about-hy.component.html delete mode 100644 src/app/pages/info/about/hy/about-hy.component.ts delete mode 100644 src/app/pages/info/about/ru/about-ru.component.html delete mode 100644 src/app/pages/info/about/ru/about-ru.component.ts delete mode 100644 src/app/pages/info/contacts/contacts.component.html delete mode 100644 src/app/pages/info/contacts/contacts.component.scss delete mode 100644 src/app/pages/info/contacts/contacts.component.ts delete mode 100644 src/app/pages/info/contacts/en/contacts-en.component.html delete mode 100644 src/app/pages/info/contacts/en/contacts-en.component.ts delete mode 100644 src/app/pages/info/contacts/hy/contacts-hy.component.html delete mode 100644 src/app/pages/info/contacts/hy/contacts-hy.component.ts delete mode 100644 src/app/pages/info/contacts/ru/contacts-ru.component.html delete mode 100644 src/app/pages/info/contacts/ru/contacts-ru.component.ts delete mode 100644 src/app/pages/info/delivery/delivery.component.html delete mode 100644 src/app/pages/info/delivery/delivery.component.scss delete mode 100644 src/app/pages/info/delivery/delivery.component.ts delete mode 100644 src/app/pages/info/delivery/en/delivery-en.component.html delete mode 100644 src/app/pages/info/delivery/en/delivery-en.component.ts delete mode 100644 src/app/pages/info/delivery/hy/delivery-hy.component.html delete mode 100644 src/app/pages/info/delivery/hy/delivery-hy.component.ts delete mode 100644 src/app/pages/info/delivery/ru/delivery-ru.component.html delete mode 100644 src/app/pages/info/delivery/ru/delivery-ru.component.ts delete mode 100644 src/app/pages/info/faq/en/faq-en.component.html delete mode 100644 src/app/pages/info/faq/en/faq-en.component.ts delete mode 100644 src/app/pages/info/faq/faq.component.html delete mode 100644 src/app/pages/info/faq/faq.component.scss delete mode 100644 src/app/pages/info/faq/faq.component.ts delete mode 100644 src/app/pages/info/faq/hy/faq-hy.component.html delete mode 100644 src/app/pages/info/faq/hy/faq-hy.component.ts delete mode 100644 src/app/pages/info/faq/ru/faq-ru.component.html delete mode 100644 src/app/pages/info/faq/ru/faq-ru.component.ts delete mode 100644 src/app/pages/info/guarantee/en/guarantee-en.component.html delete mode 100644 src/app/pages/info/guarantee/en/guarantee-en.component.ts delete mode 100644 src/app/pages/info/guarantee/guarantee.component.html delete mode 100644 src/app/pages/info/guarantee/guarantee.component.scss delete mode 100644 src/app/pages/info/guarantee/guarantee.component.ts delete mode 100644 src/app/pages/info/guarantee/hy/guarantee-hy.component.html delete mode 100644 src/app/pages/info/guarantee/hy/guarantee-hy.component.ts delete mode 100644 src/app/pages/info/guarantee/ru/guarantee-ru.component.html delete mode 100644 src/app/pages/info/guarantee/ru/guarantee-ru.component.ts delete mode 100644 src/app/pages/item-detail/item-detail.component.html delete mode 100644 src/app/pages/item-detail/item-detail.component.scss delete mode 100644 src/app/pages/item-detail/item-detail.component.ts delete mode 100644 src/app/pages/legal/company-details/company-details.component.html delete mode 100644 src/app/pages/legal/company-details/company-details.component.scss delete mode 100644 src/app/pages/legal/company-details/company-details.component.ts delete mode 100644 src/app/pages/legal/company-details/en/company-details-en.component.html delete mode 100644 src/app/pages/legal/company-details/en/company-details-en.component.ts delete mode 100644 src/app/pages/legal/company-details/hy/company-details-hy.component.html delete mode 100644 src/app/pages/legal/company-details/hy/company-details-hy.component.ts delete mode 100644 src/app/pages/legal/company-details/ru/company-details-ru.component.html delete mode 100644 src/app/pages/legal/company-details/ru/company-details-ru.component.ts delete mode 100644 src/app/pages/legal/payment-terms/en/payment-terms-en.component.html delete mode 100644 src/app/pages/legal/payment-terms/en/payment-terms-en.component.ts delete mode 100644 src/app/pages/legal/payment-terms/hy/payment-terms-hy.component.html delete mode 100644 src/app/pages/legal/payment-terms/hy/payment-terms-hy.component.ts delete mode 100644 src/app/pages/legal/payment-terms/payment-terms.component.html delete mode 100644 src/app/pages/legal/payment-terms/payment-terms.component.scss delete mode 100644 src/app/pages/legal/payment-terms/payment-terms.component.ts delete mode 100644 src/app/pages/legal/payment-terms/ru/payment-terms-ru.component.html delete mode 100644 src/app/pages/legal/payment-terms/ru/payment-terms-ru.component.ts delete mode 100644 src/app/pages/legal/privacy-policy/en/privacy-policy-en.component.html delete mode 100644 src/app/pages/legal/privacy-policy/en/privacy-policy-en.component.ts delete mode 100644 src/app/pages/legal/privacy-policy/hy/privacy-policy-hy.component.html delete mode 100644 src/app/pages/legal/privacy-policy/hy/privacy-policy-hy.component.ts delete mode 100644 src/app/pages/legal/privacy-policy/privacy-policy.component.html delete mode 100644 src/app/pages/legal/privacy-policy/privacy-policy.component.scss delete mode 100644 src/app/pages/legal/privacy-policy/privacy-policy.component.ts delete mode 100644 src/app/pages/legal/privacy-policy/ru/privacy-policy-ru.component.html delete mode 100644 src/app/pages/legal/privacy-policy/ru/privacy-policy-ru.component.ts delete mode 100644 src/app/pages/legal/public-offer/en/public-offer-en.component.html delete mode 100644 src/app/pages/legal/public-offer/en/public-offer-en.component.ts delete mode 100644 src/app/pages/legal/public-offer/hy/public-offer-hy.component.html delete mode 100644 src/app/pages/legal/public-offer/hy/public-offer-hy.component.ts delete mode 100644 src/app/pages/legal/public-offer/public-offer.component.html delete mode 100644 src/app/pages/legal/public-offer/public-offer.component.scss delete mode 100644 src/app/pages/legal/public-offer/public-offer.component.ts delete mode 100644 src/app/pages/legal/public-offer/ru/public-offer-ru.component.html delete mode 100644 src/app/pages/legal/public-offer/ru/public-offer-ru.component.ts delete mode 100644 src/app/pages/legal/return-policy/en/return-policy-en.component.html delete mode 100644 src/app/pages/legal/return-policy/en/return-policy-en.component.ts delete mode 100644 src/app/pages/legal/return-policy/hy/return-policy-hy.component.html delete mode 100644 src/app/pages/legal/return-policy/hy/return-policy-hy.component.ts delete mode 100644 src/app/pages/legal/return-policy/return-policy.component.html delete mode 100644 src/app/pages/legal/return-policy/return-policy.component.scss delete mode 100644 src/app/pages/legal/return-policy/return-policy.component.ts delete mode 100644 src/app/pages/legal/return-policy/ru/return-policy-ru.component.html delete mode 100644 src/app/pages/legal/return-policy/ru/return-policy-ru.component.ts delete mode 100644 src/app/pages/search/search.component.html delete mode 100644 src/app/pages/search/search.component.scss delete mode 100644 src/app/pages/search/search.component.ts diff --git a/docs/FRONTEND-ROADMAP.md b/docs/FRONTEND-ROADMAP.md index c3c9edc..f41b152 100644 --- a/docs/FRONTEND-ROADMAP.md +++ b/docs/FRONTEND-ROADMAP.md @@ -32,16 +32,16 @@ Full detail in `docs/KNOWN-ISSUES.md`. Summary: - Payment modal / bank-payment iframe on Cart still custom (own focus-trap, multi-step state — note: RC A11Y-01 added a real focus-trap to it, but it's still not `app-dialog` itself) — candidate for `app-dialog` migration. - Cart's native `confirm()` on clear-cart — no existing confirm-dialog pattern to follow yet. - Genuine brand-color contrast failures (`--border-color`, `--success/warning/error/info-color` as text) — flagged by RC A11Y-01, need theme-owner sign-off before changing. -- `stars.component` rating glyph color and a few legacy hex literals in `pages/category`/`pages/search` have no exact token match — needs a deliberate token-extension decision. +- `stars.component` rating glyph color has no exact token match — needs a deliberate token-extension decision (the `pages/category`/`pages/search` half of this was resolved by deleting those files, see below). - Homepage hero-to-categories dead-space gap — traces to mock bootstrap config, not a code defect; needs real-tenant-data reproduction. - Footer "Contacts" link has no static-page content in mock data — needs a content decision (found during RC walkthrough). - Builder's static-page body editor is hidden inside a collapsed "Advanced" section, mislabeled "Source HTML (advanced)" — works, but needs a navigation/labeling decision (found during RC walkthrough). - ~178 missing `adminXxx.*` i18n keys across admin backoffice. - Theme Mode (dark/system) selector has no runtime CSS effect — real feature project, not a wiring fix. -- `dynamic-renderer/` pipeline exists but is unwired — needs a finish-or-delete decision. +- ~~`dynamic-renderer/` pipeline exists but is unwired~~ — verified 2026-07-25 (RC-02 task 6): it IS wired, it's the live homepage rendering engine (`HomeComponent` → `WebsiteRuntimeFacade` → `PageRendererService`/`PageResolverService` → `DynamicPageLayoutComponent`). Prior "unwired" note was stale. - `primeng`/`primeicons` still in `package.json` despite the only consumer being deleted (RC PERF-01) — `npm uninstall` blocked by an unrelated broken `barry-cache` devDependency (`ETARGET`); fix that first. - 2 large lazy chunks (`project-editor` 320 kB, `catalog-container` 126 kB) — no mechanical split found, needs a dedicated task. -- **CRITICAL**: `pages/category`, `pages/search`, `pages/item-detail`, `pages/info/**`, `pages/legal/**` (40+ files) are entirely unrouted dead code — `cmsContentRoutes` in `app.routes.ts` is a literal empty array. Several earlier fixes (see git history) were applied to this dead code with zero production effect. See `docs/KNOWN-ISSUES.md` item 13 — needs a wire-it-up-or-delete-it decision. Tracked in `docs/TODO.md`. +- **RESOLVED 2026-07-25 (RC-02 task 6)**: `pages/category`, `pages/search`, `pages/item-detail`, `pages/info/**`, `pages/legal/**` (40+ files) were entirely unrouted dead code — deleted. See `docs/KNOWN-ISSUES.md` item 13. - Backend integration: still mostly PLANNED/mock — a "backend ready" sprint was attempted and explicitly deferred (2026-07-24) pending a real API contract (`docs/BACKEND_API.md` is the canonical spec — no live endpoint confirmation beyond what's already CURRENT). ## Not audited / out of scope diff --git a/docs/KNOWN-ISSUES.md b/docs/KNOWN-ISSUES.md index 6e2af7a..c20c740 100644 --- a/docs/KNOWN-ISSUES.md +++ b/docs/KNOWN-ISSUES.md @@ -91,10 +91,11 @@ don't fix inline unless asked. introducing the first one is an architecture decision, not polish. - Found: 2026-07-23, RC-Premium-01. -8. **`stars.component` rating glyph color and a few legacy hex literals in - `pages/category`/`pages/search` have no exact design-token match.** - Left as literals to avoid an unintended visual shift; needs a deliberate - token-extension decision before normalizing. +8. ~~`stars.component` rating glyph color and a few legacy hex literals in + `pages/category`/`pages/search` have no exact design-token match.`~~ + **Resolved 2026-07-25 (RC-02 task 6):** `pages/category`/`pages/search` + deleted as unrouted dead code (see item 13). `stars.component` literals + remain, tracked separately if still relevant. - Found: 2026-07-23, RC-Premium-01. 9. **Genuine brand-color contrast failures (WCAG AA).** `--border-color` @@ -125,9 +126,11 @@ don't fix inline unless asked. most of the remaining bundle-budget overage in one move). - Found: 2026-07-24, RC PERF-01 (`PERFORMANCE_REPORT.md` (removed, see git history)). -13. **CRITICAL — `pages/category/*`, `pages/search/*`, `pages/item-detail/*`, - `pages/info/**`, `pages/legal/**` (40+ files) are entirely unrouted dead - code, not live pages.** Verified directly against `src/app/app.routes.ts`: +13. **RESOLVED 2026-07-25 (RC-02 task 6) — `pages/category/*`, `pages/search/*`, + `pages/item-detail/*`, `pages/info/**`, `pages/legal/**` (40+ files) were + entirely unrouted dead code, not live pages.** Decision: delete (not + wire up) — each had a live replacement already serving its traffic. + Verified directly against `src/app/app.routes.ts`: `category/:id` and `category/:id/items` `redirectTo: 'catalog/:id'` (served by `CatalogContainerComponent`); `search` also routes to `CatalogContainerComponent`; `product/:id` routes to @@ -321,9 +324,9 @@ don't fix inline unless asked. hardcoded hex colors, duplicating what `app-skeleton` already provides and what `catalog-container`/`product-details-container` already use. - Fixed: 2026-07-23, RC STORE-01 (`STORE_REVIEW.md` (removed, see git history)). - - **Correction (2026-07-25):** these files are unrouted dead code (Open - item 13) — the fix is real and harmless but currently has zero - production effect. Live category/search rendering goes through + - **Superseded 2026-07-25 (RC-02 task 6):** these files were confirmed + unrouted dead code (item 13) and deleted outright, making this fix + moot. Live category/search rendering goes through `CatalogContainerComponent`, not these files. 8. **Cart's dead `.email-form` markup and CSS removed.** Post-payment diff --git a/docs/PROJECT_INDEX.md b/docs/PROJECT_INDEX.md index a89433c..0f6c6c4 100644 --- a/docs/PROJECT_INDEX.md +++ b/docs/PROJECT_INDEX.md @@ -2,10 +2,6 @@ This is the entry point. Read this first — it links to everything else and tells you what's actually true right now versus what's historical. -## ⚠️ Read before touching routing or storefront static pages - -`pages/category/*`, `pages/search/*`, `pages/item-detail/*`, `pages/info/**`, and `pages/legal/**` (40+ files) look like live storefront pages but are **entirely unrouted dead code**. `src/app/app.routes.ts`'s `cmsContentRoutes` is a literal empty array. The real routes redirect `category/:id`/`search` to `CatalogContainerComponent`, `product/:id` to `ProductDetailsContainerComponent`, and every static/legal page (About, Contacts, FAQ, etc.) is served by the catch-all `:staticPath` route resolving `bootstrap.staticPages` — not by the hardcoded components under `pages/info`/`pages/legal`. Full detail: [KNOWN-ISSUES.md](KNOWN-ISSUES.md) item 13, tracked in [TODO.md](TODO.md). Several earlier polish passes (see git history) were applied to this dead code before this was caught. - ## What this is A configuration-driven, multi-tenant SaaS marketplace platform (Angular 21.1, standalone components). One frontend codebase serves unlimited tenants ("marketplaces"). Tenant identity, theme, navigation, page/section/widget composition, and static content all resolve from a per-tenant `bootstrap.json` fetched at runtime — no tenant-specific code paths exist in the frontend. New tenants are onboarded by domain + config + backend data, never by forking the frontend. diff --git a/docs/SPRINTS.md b/docs/SPRINTS.md new file mode 100644 index 0000000..160cefa --- /dev/null +++ b/docs/SPRINTS.md @@ -0,0 +1,235 @@ +# BACKEND READY + +Backend APIs are now available. + +Read + +docs/BACKEND-API.md + +Do NOT redesign. + +Do NOT change models. + +Replace ONLY gateway implementations. + +Keep + +Facades + +Signals + +Components + +Pages + +State + +unchanged. + +Implement + +Authentication + +JWT + +Refresh + +CRUD + +Uploads + +Errors + +Retry + +Offline + +Maintenance + +Feature Flags + +Run build. + +Run typecheck. + +Generate + +docs/BACKEND_INTEGRATION.md + +Commit. + + +*************** + + +# RELEASE CANDIDATE + +Pretend this application ships tomorrow. + +Walk through EVERY route. + +Storefront + +Builder + +Backoffice + +Verify + +Console + +Network + +Performance + +Loading + +Errors + +404 + +Raw i18n + +Icons + +Images + +Animations + +Overflow + +Responsive + +Dialogs + +Buttons + +Forms + +Search + +Checkout + +Builder + +Admin + +Fix every P0/P1 issue. + +Do not redesign. + +Commit every milestone. + +Generate + +docs/RELEASE_REPORT.md + + +***************** + +# DOCUMENTATION SYNC + +Do NOT modify application code. + +Update only documentation. + +Refresh + +Graphify graph + +Obsidian + +PROJECT_INDEX.md + +FRONTEND-ROADMAP.md + +KNOWN-ISSUES.md + +ADRs if required + +Synchronize all project knowledge. + +Commit documentation separately. +******************** + + +# CLEANUP + +Inspect only for + +Dead Components + +Dead Services + +Dead Routes + +Dead CSS + +Duplicate Models + +Duplicate Interfaces + +Unused Imports + +Unused Variables + +Unused Assets + +Unused Icons + +Unused SCSS + +Unused Directories + +Unused Modules + +Do NOT touch business logic. + +Produce + +docs/CLEANUP_REPORT.md + +Delete only confirmed dead code. + +Commit. + +*************** + + +# ANGULAR UPGRADE + +Inspect + +Angular + +TypeScript + +RxJS + +Builder + +Compiler + +Dependencies + +Determine whether upgrading to Angular 22 is safe. + +Do NOT upgrade automatically. + +Produce + +docs/ANGULAR22_PLAN.md + +Include + +Benefits + +Risks + +Breaking changes + +Migration steps + +Estimated effort + +Stop. \ No newline at end of file diff --git a/docs/TODO.md b/docs/TODO.md index 6f537e6..2542bbc 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -4,7 +4,7 @@ Checklist of everything documented as open/remaining across the docs, verified a ## Critical -- [ ] `pages/category`, `pages/search`, `pages/item-detail`, `pages/info/**`, `pages/legal/**` (40+ files) — unrouted dead code, `cmsContentRoutes` in `app.routes.ts` is a literal empty array (verified still empty). Needs wire-up-or-delete decision — deliberately not resolved, out of "mechanical fix" scope. +- [x] `pages/category`, `pages/search`, `pages/item-detail`, `pages/info/**`, `pages/legal/**` (40+ files) — unrouted dead code. Resolved 2026-07-25 (RC-02 task 6): deleted, decision was delete (routes already served by `catalog-container`/`product-details-container`; info/legal superseded by CMS-driven `static-page` component). - [x] No `canDeactivate` guard on `admin/products/:id/edit` — fixed 2026-07-25 (`adminProductDirtyGuard`, mirrors categories). ## Backend — skipped, doing together diff --git a/src/app/pages/category/category.component.html b/src/app/pages/category/category.component.html deleted file mode 100644 index 8027a43..0000000 --- a/src/app/pages/category/category.component.html +++ /dev/null @@ -1,63 +0,0 @@ -
- @if (error()) { -
-

{{ error() }}

- -
- } - - @if (!error()) { -
- @for (item of items(); track trackByItemId($index, item)) { - - } - - @if (loading() && items().length > 0) { - @for (i of skeletonSlots; track i) { - - } - } -
- - @if (!hasMore() && items().length > 0) { -
-

{{ 'category.allLoaded' | translate }}

-
- } - - @if (items().length === 0 && !loading()) { -
-
- -
-

{{ 'category.emptyTitle' | translate }}

-

{{ 'category.emptyDesc' | translate }}

- {{ 'category.goHome' | translate }} -
- } - - @if (loading() && items().length === 0) { -
-
-

{{ 'category.loading' | translate }}

-
- } - } -
diff --git a/src/app/pages/category/category.component.scss b/src/app/pages/category/category.component.scss deleted file mode 100644 index 731849c..0000000 --- a/src/app/pages/category/category.component.scss +++ /dev/null @@ -1,391 +0,0 @@ -.category-container { - max-width: 1200px; - margin: 0 auto; - padding: 24px; - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; -} - -.error, -.loading-initial, -.no-more { - text-align: center; - padding: 60px 20px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - color: #697777; - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; -} - -.no-items { - text-align: center; - padding: 80px 20px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - - .no-items-icon { - width: 100px; - height: 100px; - display: flex; - align-items: center; - justify-content: center; - background: #f5f3f9; - border-radius: 50%; - margin-bottom: 24px; - } - - h3 { - font-size: var(--font-size-2xl, 1.25rem); - font-weight: var(--font-weight-bold, 700); - color: #1e3c38; - margin: 0 0 8px 0; - } - - p { - font-size: var(--font-size-md, 0.9375rem); - color: #697777; - margin: 0 0 24px 0; - max-width: 340px; - line-height: 1.5; - } - - .no-items-btn { - display: inline-flex; - align-items: center; - padding: 10px 28px; - background: #497671; - color: white; - border-radius: var(--radius-lg, 13px); - font-family: "DM Sans", sans-serif; - font-size: var(--font-size-md, 0.9375rem); - font-weight: var(--font-weight-semibold, 600); - text-decoration: none; - transition: background 0.2s ease, transform 0.15s ease; - - &:hover { - background: #3d635f; - transform: translateY(-1px); - } - } -} - -.error button { - margin-top: 20px; - padding: 10px 24px; - background: #497671; - color: white; - border: none; - border-radius: var(--radius-sm, 8px); - cursor: pointer; - font-size: var(--font-size-lg, 1rem); - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - font-weight: var(--font-weight-medium, 500); - transition: all 0.2s ease; - - &:hover { - background: #3a5f5b; - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(73, 118, 113, 0.3); - } -} - -.items-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 30px; - margin-bottom: 40px; - width: 100%; -} - -.item-card { - width: 100%; - min-width: 0; - display: flex; - flex-direction: column; - overflow: hidden; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - - &:hover { - transform: translateY(-4px); - - .item-image { - box-shadow: 0 6px 8px 0 rgba(0, 0, 0, 0.2); - } - - .item-details { - box-shadow: 0 6px 8px 0 rgba(0, 0, 0, 0.2); - } - } -} - -.item-link { - text-decoration: none; - color: inherit; - flex: 1; - display: flex; - flex-direction: column; -} - -.item-image { - position: relative; - width: 100%; - aspect-ratio: 4 / 3; - border: 1px solid #d3dad9; - border-radius: 13px 13px 0 0; - box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.15); - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - background: #f0f0f0; - - img { - width: 100%; - height: 100%; - object-fit: contain; - background: white; - padding: 12px; - transition: transform 0.3s ease, opacity 0.3s ease; - } - - &:hover img { - transform: scale(1.05); - } -} - -.discount-badge { - position: absolute; - top: 12px; - right: 12px; - background: #ff4757; - color: white; - padding: 6px 12px; - border-radius: 20px; - font-weight: var(--font-weight-semibold, 600); - font-size: var(--font-size-base, 0.875rem); - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - z-index: 1; -} - -.item-details { - width: 100%; - border: 1px solid #d3dad9; - border-top: none; - border-radius: 0 0 13px 13px; - padding: 12px 16px; - box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.15); - background: #f5f3f9; - flex: 1; - display: flex; - flex-direction: column; - gap: 6px; - transition: background 0.3s ease; -} - -.item-name { - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - font-weight: var(--font-weight-semibold, 600); - font-size: clamp(14px, 1.4vw, 18px); - color: #1e3c38; - margin: 0; - line-height: 1.3; - display: -webkit-box; - line-clamp: 2; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - min-height: calc(2 * 1.3em); -} - -.item-rating { - display: flex; - align-items: center; - gap: 6px; - font-size: var(--font-size-sm, 0.8125rem); - color: #697777; - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - - .rating-stars { - color: #ffa502; - font-weight: var(--font-weight-semibold, 600); - } -} - -.item-price { - display: flex; - align-items: center; - gap: 8px; - flex-wrap: wrap; - margin-top: auto; -} - -.original-price { - text-decoration: line-through; - color: #697777; - font-size: var(--font-size-sm, 0.8125rem); - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; -} - -.discounted-price, -.current-price { - font-size: clamp(16px, 1.6vw, 22px); - font-weight: var(--font-weight-bold, 700); - color: #497671; - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; -} - -.item-stock { - .stock-bar { - display: flex; - gap: 4px; - align-items: center; - - .bar-segment { - width: 20px; - height: 6px; - background: #d3dad9; - border-radius: 3px; - transition: background 0.2s; - - &.filled.high { - background: #497671; - } - - &.filled.medium { - background: #a1b4b5; - } - - &.filled.low { - background: #ff4757; - } - } - } -} - -.add-to-cart-btn { - width: 100%; - padding: 12px; - background: var(--primary-color); - color: white; - border: none; - border-radius: 0 0 13px 13px; - font-size: var(--font-size-md, 0.9375rem); - font-weight: var(--font-weight-semibold, 600); - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - cursor: pointer; - transition: all 0.2s ease; - margin-top: -1px; - - &:hover { - background: var(--primary-hover); - } - - &:active { - transform: scale(0.98); - } -} - -.spinner { - width: 40px; - height: 40px; - border: 4px solid #d3dad9; - border-top: 4px solid var(--primary-color); - border-radius: 50%; - animation: spin 1s linear infinite; - margin: 0 auto 12px; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -.no-more { - color: #697777; - padding: 40px 20px; -} - -// Skeleton loading cards (shimmer/animation owned by shared app-skeleton) -.skeleton-card { - pointer-events: none; - - .item-image { - display: block; - aspect-ratio: 1 / 1; - } - - .item-details app-skeleton { - display: block; - margin-bottom: var(--space-sm, 8px); - } - - .skeleton-btn { - display: block; - border-radius: 0 0 13px 13px; - margin-top: -1px; - } -} - -// Responsive -@media (max-width: 1200px) { - .items-grid { - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 24px; - } -} - -@media (max-width: 992px) { - .items-grid { - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 20px; - } -} - -@media (max-width: 768px) { - .items-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 16px; - } - - .item-name { - font-size: clamp(12px, 3vw, 16px); - } - - .discounted-price, - .current-price { - font-size: clamp(14px, 3.5vw, 18px); - } - - .item-details { - padding: 10px 12px; - } -} - -@media (max-width: 480px) { - .category-container { - padding: 12px; - } - - .items-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 12px; - } - - .item-details { - padding: 8px 10px; - } - - .item-card:hover { - transform: translateY(-2px); - } - - .add-to-cart-btn { - padding: 10px; - font-size: var(--font-size-sm, 0.8125rem); - } -} diff --git a/src/app/pages/category/category.component.ts b/src/app/pages/category/category.component.ts deleted file mode 100644 index 03dbf26..0000000 --- a/src/app/pages/category/category.component.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { Component, OnInit, OnDestroy, signal, HostListener, ChangeDetectionStrategy, inject } from '@angular/core'; -import { ActivatedRoute, RouterLink } from '@angular/router'; -import { CartService } from '../../services'; -import { PrefetchService } from '../../services/prefetch.service'; -import { Item } from '../../models'; -import { Subscription } from 'rxjs'; -import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField } from '../../utils/item.utils'; -import { LanguageService } from '../../services/language.service'; -import { LangRoutePipe } from '../../pipes/lang-route.pipe'; -import { TranslatePipe } from '../../i18n/translate.pipe'; -import { SCROLL_THRESHOLD_PX, SCROLL_DEBOUNCE_MS, ITEMS_PER_PAGE } from '../../config/constants'; -import { ProductFacade } from '../../facades/platform/product.facade'; -import { ProductCardComponent } from '../../components/product-card/product-card.component'; -import { IconComponent } from '../../shared/ui/icon/icon.component'; -import { SkeletonComponent } from '../../shared/ui/skeleton/skeleton.component'; - -@Component({ - selector: 'app-category', - imports: [RouterLink, LangRoutePipe, TranslatePipe, ProductCardComponent, IconComponent, SkeletonComponent], - templateUrl: './category.component.html', - styleUrls: ['./category.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class CategoryComponent implements OnInit, OnDestroy { - categoryId = signal(0); - items = signal([]); - loading = signal(false); - error = signal(null); - hasMore = signal(true); - - private skip = 0; - private readonly count = ITEMS_PER_PAGE; - private isLoadingMore = false; - private routeSubscription?: Subscription; - private scrollTimeout?: ReturnType; - private readonly productFacade = inject(ProductFacade); - - constructor( - private route: ActivatedRoute, - private cartService: CartService, - private prefetchService: PrefetchService - ) {} - - ngOnInit(): void { - this.routeSubscription = this.route.params.subscribe(params => { - const id = parseInt(params['id'], 10); - this.categoryId.set(id); - this.resetAndLoad(); - }); - } - - ngOnDestroy(): void { - this.routeSubscription?.unsubscribe(); - if (this.scrollTimeout) clearTimeout(this.scrollTimeout); - } - - resetAndLoad(): void { - this.items.set([]); - this.skip = 0; - this.hasMore.set(true); - this.loadItems(); - } - - loadItems(): void { - if (this.isLoadingMore || !this.hasMore()) return; - - this.loading.set(true); - this.isLoadingMore = true; - - this.productFacade.getProductsByCategory(this.categoryId(), { count: this.count, skip: this.skip }).subscribe({ - next: (result) => { - const newItems = result.items; - // Handle null or empty response - if (!newItems || newItems.length === 0) { - this.hasMore.set(false); - } else { - if (newItems.length < this.count) { - this.hasMore.set(false); - } - this.items.update(current => [...current, ...newItems]); - this.skip += this.count; - } - this.loading.set(false); - this.isLoadingMore = false; - }, - error: (err) => { - this.error.set('Failed to load items'); - this.loading.set(false); - this.isLoadingMore = false; - console.error('Error loading items:', err); - } - }); - } - - @HostListener('window:scroll') - onScroll(): void { - if (this.scrollTimeout) clearTimeout(this.scrollTimeout); - - this.scrollTimeout = setTimeout(() => { - const scrollPosition = window.innerHeight + window.scrollY; - const bottomPosition = document.documentElement.scrollHeight - SCROLL_THRESHOLD_PX; - - if (scrollPosition >= bottomPosition && !this.loading() && this.hasMore() && !this.isLoadingMore) { - this.loadItems(); - } - }, SCROLL_DEBOUNCE_MS); - } - - addToCart(itemID: number, event: Event): void { - event.preventDefault(); - event.stopPropagation(); - this.cartService.addItem(itemID); - } - - onItemHover(itemID: number): void { - this.prefetchService.prefetchItem(itemID); - } - - readonly skeletonSlots = Array.from({ length: 8 }); - readonly getDiscountedPrice = getDiscountedPrice; - readonly getMainImage = getMainImage; - readonly trackByItemId = trackByItemId; - readonly getBadgeClass = getBadgeClass; - - private langService = inject(LanguageService); - itemName(item: Item): string { return getTranslatedField(item, 'name', this.langService.currentLanguage()); } -} diff --git a/src/app/pages/category/subcategories.component.html b/src/app/pages/category/subcategories.component.html deleted file mode 100644 index 02a6126..0000000 --- a/src/app/pages/category/subcategories.component.html +++ /dev/null @@ -1,102 +0,0 @@ -
- @if (loading()) { -
-
-

{{ 'subcategories.loading' | translate }}

-
- } - - @if (error()) { -
-

{{ error() }}

- -
- } - - @if (!loading() && !error()) { -
-

{{ parentName() }}

-
- - @if (subcategories().length > 0) { - - } - - - @if (categoryItems().length > 0) { - - } - - @if (!hasSubcategories() && categoryItems().length === 0) { -
-
- -
-

{{ 'subcategories.emptyTitle' | translate }}

-

{{ 'subcategories.emptyDesc' | translate }}

- {{ 'subcategories.goHome' | translate }} -
- } - } -
diff --git a/src/app/pages/category/subcategories.component.scss b/src/app/pages/category/subcategories.component.scss deleted file mode 100644 index a555e9f..0000000 --- a/src/app/pages/category/subcategories.component.scss +++ /dev/null @@ -1,491 +0,0 @@ -.subcategories-container { - max-width: 1200px; - margin: 0 auto; - padding: 24px; - - // Loading state - .loading { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 400px; - gap: 16px; - - .spinner { - width: 48px; - height: 48px; - border: 4px solid #d3dad9; - border-top-color: #497671; - border-radius: 50%; - animation: spin 0.8s linear infinite; - } - - p { - color: #697777; - font-size: var(--font-size-lg, 1rem); - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - margin: 0; - } - } - - // Error state - .error { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 400px; - gap: 16px; - padding: 24px; - - p { - color: #dc2626; - font-size: var(--font-size-lg, 1rem); - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - text-align: center; - margin: 0; - } - - button { - padding: 10px 24px; - background: #497671; - color: white; - border: none; - border-radius: var(--radius-sm, 8px); - font-size: var(--font-size-md, 0.9375rem); - font-weight: var(--font-weight-medium, 500); - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - cursor: pointer; - transition: all 0.2s ease; - - &:hover { - background: #3a5f5b; - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(73, 118, 113, 0.3); - } - - &:active { - transform: translateY(0); - } - } - } - - // Empty subcategories state - .no-subcats { - text-align: center; - padding: 80px 20px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - - .no-subcats-icon { - width: 100px; - height: 100px; - display: flex; - align-items: center; - justify-content: center; - background: #f5f3f9; - border-radius: 50%; - margin-bottom: 24px; - } - - h3 { - font-size: var(--font-size-2xl, 1.25rem); - font-weight: var(--font-weight-bold, 700); - color: #1e3c38; - margin: 0 0 8px 0; - } - - p { - font-size: var(--font-size-md, 0.9375rem); - color: #697777; - margin: 0 0 24px 0; - max-width: 340px; - line-height: 1.5; - } - - .no-subcats-btn { - display: inline-flex; - align-items: center; - padding: 10px 28px; - background: #497671; - color: white; - border-radius: var(--radius-lg, 13px); - font-family: "DM Sans", sans-serif; - font-size: var(--font-size-md, 0.9375rem); - font-weight: var(--font-weight-semibold, 600); - text-decoration: none; - transition: background 0.2s ease, transform 0.15s ease; - - &:hover { - background: #3d635f; - transform: translateY(-1px); - } - } - } - - .sub-header { - margin-bottom: 28px; - - h2 { - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - font-size: var(--font-size-4xl, 2rem); - color: #1e3c38; - margin: 0; - font-weight: var(--font-weight-bold, 700); - letter-spacing: -0.02em; - } - } - - .categories-grid { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 30px; - width: 100%; - } - - .category-card { - width: 100%; - display: flex; - flex-direction: column; - text-decoration: none; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - - &:hover { - transform: translateY(-4px); - - .category-image { - box-shadow: 0 6px 8px 0 rgba(0, 0, 0, 0.2); - } - - .category-info { - box-shadow: 0 6px 8px 0 rgba(0, 0, 0, 0.2); - } - } - } - - .category-image { - width: 100%; - aspect-ratio: 4 / 3; - border: 1px solid #d3dad9; - border-radius: 13px 13px 0 0; - box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.15); - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - background: #f5f5f5; - - img { - width: 100%; - height: 100%; - object-fit: cover; - transition: transform 0.3s ease; - } - - &:hover img { - transform: scale(1.05); - } - } - - .category-fallback { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - font-size: 5rem; - font-weight: var(--font-weight-bold, 700); - color: #497671; - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%); - } - - .category-info { - width: 100%; - border: 1px solid #d3dad9; - border-top: none; - border-radius: 0 0 13px 13px; - padding: 12px 16px; - box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.15); - background: #f5f3f9; - display: flex; - flex-direction: column; - justify-content: center; - gap: 2px; - transition: background 0.3s ease; - } - - .category-name { - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - font-weight: var(--font-weight-semibold, 600); - font-size: clamp(14px, 1.4vw, 18px); - color: #1e3c38; - margin: 0; - line-height: 1.3; - display: -webkit-box; - line-clamp: 2; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - min-height: calc(2 * 1.3em); - } - - .category-count { - font-family: "DM Sans", sans-serif; - font-size: var(--font-size-sm, 0.8125rem); - color: #697777; - } - - .category-meta { - display: flex; - flex-wrap: wrap; - gap: 6px; - min-height: 20px; - font-family: "DM Sans", sans-serif; - font-size: var(--font-size-sm, 0.8125rem); - color: #697777; - - span { - padding: 2px 7px; - border-radius: var(--radius-full, 999px); - background: rgba(73, 118, 113, 0.1); - color: #3d635f; - white-space: nowrap; - } - } - - .category-mixed { - width: fit-content; - padding: 2px 7px; - border-radius: var(--radius-full, 999px); - background: #eef1f1; - color: #697777; - font-family: "DM Sans", sans-serif; - font-size: var(--font-size-xs, 0.75rem); - } - - // Items section within subcategories page - .category-items-section { - margin-top: 40px; - - .items-section-title { - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - font-size: var(--font-size-3xl, 1.5rem); - font-weight: var(--font-weight-bold, 700); - color: #1e3c38; - margin: 0 0 20px 0; - } - } - - .items-grid { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 24px; - } - - .item-card { - display: flex; - flex-direction: column; - text-decoration: none; - border: 1px solid #d3dad9; - border-radius: var(--radius-lg, 13px); - overflow: hidden; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - background: #fff; - - &:hover { - transform: translateY(-4px); - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); - } - } - - .item-image { - position: relative; - aspect-ratio: 1; - overflow: hidden; - background: #f5f5f5; - - img { - width: 100%; - height: 100%; - object-fit: cover; - transition: transform 0.3s ease; - } - - .item-card:hover & img { - transform: scale(1.05); - } - - .item-discount { - position: absolute; - top: 8px; - right: 8px; - background: #dc2626; - color: white; - font-size: var(--font-size-xs, 0.75rem); - font-weight: var(--font-weight-bold, 700); - padding: 2px 8px; - border-radius: 6px; - } - - .item-badges { - position: absolute; - top: 8px; - left: 8px; - display: flex; - gap: 4px; - flex-wrap: wrap; - } - - .item-badge { - font-size: 0.65rem; - font-weight: var(--font-weight-semibold, 600); - padding: 2px 6px; - border-radius: var(--radius-xs, 4px); - text-transform: uppercase; - } - } - - .item-info { - padding: 12px; - display: flex; - flex-direction: column; - gap: 8px; - } - - .item-name { - font-family: "DM Sans", sans-serif; - font-size: var(--font-size-base, 0.875rem); - font-weight: var(--font-weight-semibold, 600); - color: #1e3c38; - margin: 0; - line-height: 1.3; - display: -webkit-box; - line-clamp: 2; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - } - - .item-price { - display: flex; - align-items: center; - gap: 8px; - flex-wrap: wrap; - - .old-price { - font-size: var(--font-size-sm, 0.8125rem); - color: #a1b4b5; - text-decoration: line-through; - } - - .current-price { - font-size: var(--font-size-lg, 1rem); - font-weight: var(--font-weight-bold, 700); - color: #1e3c38; - } - } - - .item-cart-btn { - align-self: flex-end; - background: var(--primary-color); - color: white; - border: none; - border-radius: var(--radius-sm, 8px); - padding: 6px 10px; - cursor: pointer; - transition: background 0.2s ease; - - &:hover { - background: var(--primary-hover); - } - } - - // Keyframes - @keyframes spin { - to { transform: rotate(360deg); } - } - - // Responsive - @media (max-width: 1200px) { - padding: 24px; - - .categories-grid { - grid-template-columns: repeat(3, 1fr); - gap: 24px; - } - - .items-grid { - grid-template-columns: repeat(3, 1fr); - gap: 20px; - } - } - - @media (max-width: 992px) { - .categories-grid { - grid-template-columns: repeat(3, 1fr); - gap: 20px; - } - } - - @media (max-width: 768px) { - padding: 16px; - - .sub-header { - margin-bottom: 20px; - - h2 { - font-size: var(--font-size-3xl, 1.5rem); - } - } - - .categories-grid { - grid-template-columns: repeat(2, 1fr); - gap: 16px; - } - - .items-grid { - grid-template-columns: repeat(2, 1fr); - gap: 16px; - } - - .category-info { - padding: 10px 12px; - } - } - - @media (max-width: 480px) { - padding: 12px; - - .sub-header { - margin-bottom: 16px; - - h2 { - font-size: var(--font-size-3xl, 1.5rem); - } - } - - .categories-grid { - grid-template-columns: repeat(2, 1fr); - gap: 12px; - } - - .items-grid { - grid-template-columns: repeat(2, 1fr); - gap: 12px; - } - - .category-info { - padding: 8px 10px; - } - - .category-card:hover { - transform: translateY(-2px); - } - } -} diff --git a/src/app/pages/category/subcategories.component.ts b/src/app/pages/category/subcategories.component.ts deleted file mode 100644 index 67866b1..0000000 --- a/src/app/pages/category/subcategories.component.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { Component, OnInit, OnDestroy, signal, ChangeDetectionStrategy, inject } from '@angular/core'; -import { DecimalPipe } from '@angular/common'; -import { ActivatedRoute, Router, RouterLink } from '@angular/router'; -import { CartService, LanguageService } from '../../services'; -import { Item } from '../../models'; -import { combineLatest, Subscription } from 'rxjs'; -import { LangRoutePipe } from '../../pipes/lang-route.pipe'; -import { TranslatePipe } from '../../i18n/translate.pipe'; -import { TranslateService } from '../../i18n/translate.service'; -import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField } from '../../utils/item.utils'; -import { ProductFacade } from '../../facades/platform/product.facade'; -import { CategoryFacade } from '../../facades/platform/category.facade'; -import { Category } from '../../core/categories/models/category-domain.model'; -import { IconComponent } from '../../shared/ui/icon/icon.component'; - -@Component({ - selector: 'app-subcategories', - imports: [DecimalPipe, RouterLink, LangRoutePipe, TranslatePipe, IconComponent], - templateUrl: './subcategories.component.html', - styleUrls: ['./subcategories.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class SubcategoriesComponent implements OnInit, OnDestroy { - subcategories = signal([]); - /** Items belonging directly to this category (when hasItems is true) */ - categoryItems = signal([]); - loading = signal(true); - error = signal(null); - - private i18n = inject(TranslateService); - private readonly productFacade = inject(ProductFacade); - parentName = signal(''); - - private routeSubscription?: Subscription; - - constructor( - private route: ActivatedRoute, - private router: Router, - private categoryFacade: CategoryFacade, - private langService: LanguageService, - private cartService: CartService - ) {} - - ngOnInit(): void { - this.routeSubscription = this.route.params.subscribe(params => { - const id = parseInt(params['id'], 10); - this.loadForParent(id); - }); - } - - ngOnDestroy(): void { - this.routeSubscription?.unsubscribe(); - } - - private loadForParent(parentId: number): void { - this.loading.set(true); - this.categoryItems.set([]); - this.subcategories.set([]); - this.error.set(null); - this.categoryFacade.selectCategory(parentId); - - combineLatest([ - this.categoryFacade.getCategoryById(parentId), - this.categoryFacade.getChildren(parentId), - ]).subscribe({ - next: ([parent, children]) => { - this.parentName.set(parent ? this.categoryName(parent) : this.i18n.t('home.categoriesTitle')); - const visibleChildren = children - .filter(category => this.isDisplayableCategory(category)) - .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0)); - - if (visibleChildren.length > 0) { - this.subcategories.set(visibleChildren); - this.loadCategoryItems(parentId); - } else { - const lang = this.langService.currentLanguage(); - this.router.navigate([`/${lang}/category`, parentId, 'items'], { replaceUrl: true }); - } - - this.loading.set(false); - }, - error: (err) => { - this.error.set('Failed to load subcategories'); - this.loading.set(false); - console.error('Error loading categories:', err); - } - }); - } - - /** Load items that belong directly to this category */ - private loadCategoryItems(categoryId: number): void { - this.productFacade.getProductsByCategory(categoryId, { count: 50, skip: 0 }).subscribe({ - next: (result) => { - this.categoryItems.set(result.items); - }, - error: () => { - // Not critical — subcategories still work - } - }); - } - - private isDisplayableCategory(category: Category): boolean { - return category.visible !== false - && ((category.itemCount ?? 0) > 0 || category.children.length > 0); - } - - hasSubcategories(): boolean { - return this.subcategories().length > 0; - } - - addToCart(itemID: number, event: Event): void { - event.preventDefault(); - event.stopPropagation(); - this.cartService.addItem(itemID); - } - - // TrackBy function for performance optimization - trackByCategoryId(_index: number, category: Category): number { - return category.id; - } - - subcategoryChildCount(subcategory: Category): number { - return subcategory.children.length; - } - - subcategoryItemCount(subcategory: Category): number { - return subcategory.itemCount ?? 0; - } - - readonly getDiscountedPrice = getDiscountedPrice; - readonly getMainImage = getMainImage; - readonly trackByItemId = trackByItemId; - readonly getBadgeClass = getBadgeClass; - - itemName(item: Item): string { return getTranslatedField(item, 'name', this.langService.currentLanguage()); } - - categoryName(cat: Category): string { return cat.translations[this.langService.currentLanguage()]?.title ?? cat.title; } -} diff --git a/src/app/pages/info/about/about.component.html b/src/app/pages/info/about/about.component.html deleted file mode 100644 index 3e696b8..0000000 --- a/src/app/pages/info/about/about.component.html +++ /dev/null @@ -1,5 +0,0 @@ -@switch (lang()) { - @case ('ru') { } - @case ('en') { } - @case ('hy') { } -} diff --git a/src/app/pages/info/about/about.component.scss b/src/app/pages/info/about/about.component.scss deleted file mode 100644 index c410e98..0000000 --- a/src/app/pages/info/about/about.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@use '../../../../styles/shared-legal.scss'; - diff --git a/src/app/pages/info/about/about.component.ts b/src/app/pages/info/about/about.component.ts deleted file mode 100644 index 5e07e30..0000000 --- a/src/app/pages/info/about/about.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; -import { LanguageService } from '../../../services/language.service'; -import { AboutRuComponent } from './ru/about-ru.component'; -import { AboutEnComponent } from './en/about-en.component'; -import { AboutHyComponent } from './hy/about-hy.component'; - -@Component({ - selector: 'app-about', - imports: [AboutRuComponent, AboutEnComponent, AboutHyComponent], - templateUrl: './about.component.html', - styleUrls: ['./about.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class AboutComponent { - lang = inject(LanguageService).currentLanguage; -} diff --git a/src/app/pages/info/about/en/about-en.component.html b/src/app/pages/info/about/en/about-en.component.html deleted file mode 100644 index 5ea7d5b..0000000 --- a/src/app/pages/info/about/en/about-en.component.html +++ /dev/null @@ -1,77 +0,0 @@ - diff --git a/src/app/pages/info/about/en/about-en.component.ts b/src/app/pages/info/about/en/about-en.component.ts deleted file mode 100644 index d0ce0d7..0000000 --- a/src/app/pages/info/about/en/about-en.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-about-en', - templateUrl: './about-en.component.html', - styleUrls: ['../about.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class AboutEnComponent {} diff --git a/src/app/pages/info/about/hy/about-hy.component.html b/src/app/pages/info/about/hy/about-hy.component.html deleted file mode 100644 index f9fa1d1..0000000 --- a/src/app/pages/info/about/hy/about-hy.component.html +++ /dev/null @@ -1,76 +0,0 @@ - diff --git a/src/app/pages/info/about/hy/about-hy.component.ts b/src/app/pages/info/about/hy/about-hy.component.ts deleted file mode 100644 index da9c290..0000000 --- a/src/app/pages/info/about/hy/about-hy.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-about-hy', - templateUrl: './about-hy.component.html', - styleUrls: ['../about.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class AboutHyComponent {} diff --git a/src/app/pages/info/about/ru/about-ru.component.html b/src/app/pages/info/about/ru/about-ru.component.html deleted file mode 100644 index 216f55d..0000000 --- a/src/app/pages/info/about/ru/about-ru.component.html +++ /dev/null @@ -1,77 +0,0 @@ - diff --git a/src/app/pages/info/about/ru/about-ru.component.ts b/src/app/pages/info/about/ru/about-ru.component.ts deleted file mode 100644 index cebaf7c..0000000 --- a/src/app/pages/info/about/ru/about-ru.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-about-ru', - templateUrl: './about-ru.component.html', - styleUrls: ['../about.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class AboutRuComponent {} diff --git a/src/app/pages/info/contacts/contacts.component.html b/src/app/pages/info/contacts/contacts.component.html deleted file mode 100644 index 0a22c36..0000000 --- a/src/app/pages/info/contacts/contacts.component.html +++ /dev/null @@ -1,5 +0,0 @@ -@switch (lang()) { - @case ('ru') { } - @case ('en') { } - @case ('hy') { } -} diff --git a/src/app/pages/info/contacts/contacts.component.scss b/src/app/pages/info/contacts/contacts.component.scss deleted file mode 100644 index efebbfd..0000000 --- a/src/app/pages/info/contacts/contacts.component.scss +++ /dev/null @@ -1,46 +0,0 @@ -@use '../../../../styles/shared-legal.scss'; - -.contact-item { - display: flex; - align-items: center; - gap: 1rem; - padding: 1rem; - background: var(--bg-secondary); - border-radius: var(--radius-md); - margin-bottom: 1rem; - transition: all 0.3s ease; - border-left: 3px solid var(--primary-color); - - &:hover { - transform: translateX(5px); - box-shadow: var(--shadow-sm); - } - - .icon { - font-size: var(--font-size-3xl, 1.5rem); - color: var(--primary-color); - } - - .info { - flex: 1; - - strong { - display: block; - margin-bottom: 0.25rem; - color: var(--text-secondary); - font-size: var(--font-size-sm, 0.8125rem); - } - - a { - color: var(--text-primary); - text-decoration: none; - font-weight: var(--font-weight-medium, 500); - - &:hover { - color: var(--primary-color); - text-decoration: underline; - } - } - } -} - diff --git a/src/app/pages/info/contacts/contacts.component.ts b/src/app/pages/info/contacts/contacts.component.ts deleted file mode 100644 index 368ff61..0000000 --- a/src/app/pages/info/contacts/contacts.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; -import { LanguageService } from '../../../services/language.service'; -import { ContactsRuComponent } from './ru/contacts-ru.component'; -import { ContactsEnComponent } from './en/contacts-en.component'; -import { ContactsHyComponent } from './hy/contacts-hy.component'; - -@Component({ - selector: 'app-contacts', - imports: [ContactsRuComponent, ContactsEnComponent, ContactsHyComponent], - templateUrl: './contacts.component.html', - styleUrls: ['./contacts.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ContactsComponent { - lang = inject(LanguageService).currentLanguage; -} diff --git a/src/app/pages/info/contacts/en/contacts-en.component.html b/src/app/pages/info/contacts/en/contacts-en.component.html deleted file mode 100644 index 7c563c0..0000000 --- a/src/app/pages/info/contacts/en/contacts-en.component.html +++ /dev/null @@ -1,49 +0,0 @@ - diff --git a/src/app/pages/info/contacts/en/contacts-en.component.ts b/src/app/pages/info/contacts/en/contacts-en.component.ts deleted file mode 100644 index f259a6b..0000000 --- a/src/app/pages/info/contacts/en/contacts-en.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-contacts-en', - templateUrl: './contacts-en.component.html', - styleUrls: ['../contacts.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ContactsEnComponent {} diff --git a/src/app/pages/info/contacts/hy/contacts-hy.component.html b/src/app/pages/info/contacts/hy/contacts-hy.component.html deleted file mode 100644 index 53aadfc..0000000 --- a/src/app/pages/info/contacts/hy/contacts-hy.component.html +++ /dev/null @@ -1,48 +0,0 @@ - diff --git a/src/app/pages/info/contacts/hy/contacts-hy.component.ts b/src/app/pages/info/contacts/hy/contacts-hy.component.ts deleted file mode 100644 index d6af768..0000000 --- a/src/app/pages/info/contacts/hy/contacts-hy.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-contacts-hy', - templateUrl: './contacts-hy.component.html', - styleUrls: ['../contacts.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ContactsHyComponent {} diff --git a/src/app/pages/info/contacts/ru/contacts-ru.component.html b/src/app/pages/info/contacts/ru/contacts-ru.component.html deleted file mode 100644 index 4193a10..0000000 --- a/src/app/pages/info/contacts/ru/contacts-ru.component.html +++ /dev/null @@ -1,49 +0,0 @@ - diff --git a/src/app/pages/info/contacts/ru/contacts-ru.component.ts b/src/app/pages/info/contacts/ru/contacts-ru.component.ts deleted file mode 100644 index e1f7d38..0000000 --- a/src/app/pages/info/contacts/ru/contacts-ru.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-contacts-ru', - templateUrl: './contacts-ru.component.html', - styleUrls: ['../contacts.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ContactsRuComponent {} diff --git a/src/app/pages/info/delivery/delivery.component.html b/src/app/pages/info/delivery/delivery.component.html deleted file mode 100644 index 3a9ba73..0000000 --- a/src/app/pages/info/delivery/delivery.component.html +++ /dev/null @@ -1,5 +0,0 @@ -@switch (lang()) { - @case ('ru') { } - @case ('en') { } - @case ('hy') { } -} diff --git a/src/app/pages/info/delivery/delivery.component.scss b/src/app/pages/info/delivery/delivery.component.scss deleted file mode 100644 index c410e98..0000000 --- a/src/app/pages/info/delivery/delivery.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@use '../../../../styles/shared-legal.scss'; - diff --git a/src/app/pages/info/delivery/delivery.component.ts b/src/app/pages/info/delivery/delivery.component.ts deleted file mode 100644 index 8acb90e..0000000 --- a/src/app/pages/info/delivery/delivery.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; -import { LanguageService } from '../../../services/language.service'; -import { DeliveryRuComponent } from './ru/delivery-ru.component'; -import { DeliveryEnComponent } from './en/delivery-en.component'; -import { DeliveryHyComponent } from './hy/delivery-hy.component'; - -@Component({ - selector: 'app-delivery', - imports: [DeliveryRuComponent, DeliveryEnComponent, DeliveryHyComponent], - templateUrl: './delivery.component.html', - styleUrls: ['./delivery.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class DeliveryComponent { - lang = inject(LanguageService).currentLanguage; -} diff --git a/src/app/pages/info/delivery/en/delivery-en.component.html b/src/app/pages/info/delivery/en/delivery-en.component.html deleted file mode 100644 index 3572a9f..0000000 --- a/src/app/pages/info/delivery/en/delivery-en.component.html +++ /dev/null @@ -1,60 +0,0 @@ - diff --git a/src/app/pages/info/delivery/en/delivery-en.component.ts b/src/app/pages/info/delivery/en/delivery-en.component.ts deleted file mode 100644 index 9ffb189..0000000 --- a/src/app/pages/info/delivery/en/delivery-en.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-delivery-en', - templateUrl: './delivery-en.component.html', - styleUrls: ['../delivery.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class DeliveryEnComponent {} diff --git a/src/app/pages/info/delivery/hy/delivery-hy.component.html b/src/app/pages/info/delivery/hy/delivery-hy.component.html deleted file mode 100644 index 1ff2826..0000000 --- a/src/app/pages/info/delivery/hy/delivery-hy.component.html +++ /dev/null @@ -1,60 +0,0 @@ - diff --git a/src/app/pages/info/delivery/hy/delivery-hy.component.ts b/src/app/pages/info/delivery/hy/delivery-hy.component.ts deleted file mode 100644 index bfdcda8..0000000 --- a/src/app/pages/info/delivery/hy/delivery-hy.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-delivery-hy', - templateUrl: './delivery-hy.component.html', - styleUrls: ['../delivery.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class DeliveryHyComponent {} diff --git a/src/app/pages/info/delivery/ru/delivery-ru.component.html b/src/app/pages/info/delivery/ru/delivery-ru.component.html deleted file mode 100644 index c267839..0000000 --- a/src/app/pages/info/delivery/ru/delivery-ru.component.html +++ /dev/null @@ -1,60 +0,0 @@ - diff --git a/src/app/pages/info/delivery/ru/delivery-ru.component.ts b/src/app/pages/info/delivery/ru/delivery-ru.component.ts deleted file mode 100644 index b4f6007..0000000 --- a/src/app/pages/info/delivery/ru/delivery-ru.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-delivery-ru', - templateUrl: './delivery-ru.component.html', - styleUrls: ['../delivery.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class DeliveryRuComponent {} diff --git a/src/app/pages/info/faq/en/faq-en.component.html b/src/app/pages/info/faq/en/faq-en.component.html deleted file mode 100644 index 01883fc..0000000 --- a/src/app/pages/info/faq/en/faq-en.component.html +++ /dev/null @@ -1,304 +0,0 @@ - diff --git a/src/app/pages/info/faq/en/faq-en.component.ts b/src/app/pages/info/faq/en/faq-en.component.ts deleted file mode 100644 index 3cb7cf2..0000000 --- a/src/app/pages/info/faq/en/faq-en.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { RouterLink } from '@angular/router'; -import { LangRoutePipe } from '../../../../pipes/lang-route.pipe'; - -@Component({ - selector: 'app-faq-en', - imports: [RouterLink, LangRoutePipe], - templateUrl: './faq-en.component.html', - styleUrls: ['../faq.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class FaqEnComponent {} diff --git a/src/app/pages/info/faq/faq.component.html b/src/app/pages/info/faq/faq.component.html deleted file mode 100644 index bc79cd2..0000000 --- a/src/app/pages/info/faq/faq.component.html +++ /dev/null @@ -1,5 +0,0 @@ -@switch (lang()) { - @case ('ru') { } - @case ('en') { } - @case ('hy') { } -} \ No newline at end of file diff --git a/src/app/pages/info/faq/faq.component.scss b/src/app/pages/info/faq/faq.component.scss deleted file mode 100644 index 742135a..0000000 --- a/src/app/pages/info/faq/faq.component.scss +++ /dev/null @@ -1,62 +0,0 @@ -@use '../../../../styles/shared-legal.scss'; - -// Each FAQ entry is a native
/ disclosure so long answer -// lists collapse to scannable questions; the expander chevron and its -// rotation come from the global details>summary rule in styles.scss. -.faq-item { - margin-bottom: 1rem; - padding: 1.25rem 1.5rem; - background: var(--bg-primary); - border-radius: var(--radius-md); - box-shadow: var(--shadow-sm); - transition: box-shadow var(--transition-normal), border-color var(--transition-normal); - border-left: 4px solid var(--primary-color); - - &:hover { - box-shadow: var(--shadow-md); - } - - &[open] { - box-shadow: var(--shadow-md); - } - - summary { - color: var(--primary-color); - font-size: var(--font-size-xl); - font-weight: var(--font-weight-semibold); - line-height: var(--line-height-tight); - cursor: pointer; - border-radius: var(--radius-sm); - - &:focus-visible { - outline: 2px solid var(--primary-color); - outline-offset: 2px; - } - } - - &[open] summary { - margin-bottom: 0.75rem; - } - - .faq-answer { - p { - margin-bottom: 0.5rem; - line-height: var(--line-height-relaxed); - - &:last-child { - margin-bottom: 0; - } - } - - ul { - margin-top: 0.5rem; - } - } -} - -@media (prefers-reduced-motion: reduce) { - .faq-item { - transition: none; - } -} - diff --git a/src/app/pages/info/faq/faq.component.ts b/src/app/pages/info/faq/faq.component.ts deleted file mode 100644 index 2853d3e..0000000 --- a/src/app/pages/info/faq/faq.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; -import { LanguageService } from '../../../services/language.service'; -import { FaqRuComponent } from './ru/faq-ru.component'; -import { FaqEnComponent } from './en/faq-en.component'; -import { FaqHyComponent } from './hy/faq-hy.component'; - -@Component({ - selector: 'app-faq', - imports: [FaqRuComponent, FaqEnComponent, FaqHyComponent], - templateUrl: './faq.component.html', - styleUrls: ['./faq.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class FaqComponent { - lang = inject(LanguageService).currentLanguage; -} diff --git a/src/app/pages/info/faq/hy/faq-hy.component.html b/src/app/pages/info/faq/hy/faq-hy.component.html deleted file mode 100644 index 9beadae..0000000 --- a/src/app/pages/info/faq/hy/faq-hy.component.html +++ /dev/null @@ -1,304 +0,0 @@ - diff --git a/src/app/pages/info/faq/hy/faq-hy.component.ts b/src/app/pages/info/faq/hy/faq-hy.component.ts deleted file mode 100644 index d5bd814..0000000 --- a/src/app/pages/info/faq/hy/faq-hy.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { RouterLink } from '@angular/router'; -import { LangRoutePipe } from '../../../../pipes/lang-route.pipe'; - -@Component({ - selector: 'app-faq-hy', - imports: [RouterLink, LangRoutePipe], - templateUrl: './faq-hy.component.html', - styleUrls: ['../faq.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class FaqHyComponent {} diff --git a/src/app/pages/info/faq/ru/faq-ru.component.html b/src/app/pages/info/faq/ru/faq-ru.component.html deleted file mode 100644 index 3d6a047..0000000 --- a/src/app/pages/info/faq/ru/faq-ru.component.html +++ /dev/null @@ -1,304 +0,0 @@ - diff --git a/src/app/pages/info/faq/ru/faq-ru.component.ts b/src/app/pages/info/faq/ru/faq-ru.component.ts deleted file mode 100644 index da59160..0000000 --- a/src/app/pages/info/faq/ru/faq-ru.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { RouterLink } from '@angular/router'; -import { LangRoutePipe } from '../../../../pipes/lang-route.pipe'; - -@Component({ - selector: 'app-faq-ru', - imports: [RouterLink, LangRoutePipe], - templateUrl: './faq-ru.component.html', - styleUrls: ['../faq.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class FaqRuComponent {} diff --git a/src/app/pages/info/guarantee/en/guarantee-en.component.html b/src/app/pages/info/guarantee/en/guarantee-en.component.html deleted file mode 100644 index 65a6197..0000000 --- a/src/app/pages/info/guarantee/en/guarantee-en.component.html +++ /dev/null @@ -1,158 +0,0 @@ - diff --git a/src/app/pages/info/guarantee/en/guarantee-en.component.ts b/src/app/pages/info/guarantee/en/guarantee-en.component.ts deleted file mode 100644 index 0eb2ac9..0000000 --- a/src/app/pages/info/guarantee/en/guarantee-en.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-guarantee-en', - templateUrl: './guarantee-en.component.html', - styleUrls: ['../guarantee.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class GuaranteeEnComponent {} diff --git a/src/app/pages/info/guarantee/guarantee.component.html b/src/app/pages/info/guarantee/guarantee.component.html deleted file mode 100644 index a634fec..0000000 --- a/src/app/pages/info/guarantee/guarantee.component.html +++ /dev/null @@ -1,5 +0,0 @@ -@switch (lang()) { - @case ('ru') { } - @case ('en') { } - @case ('hy') { } -} diff --git a/src/app/pages/info/guarantee/guarantee.component.scss b/src/app/pages/info/guarantee/guarantee.component.scss deleted file mode 100644 index c410e98..0000000 --- a/src/app/pages/info/guarantee/guarantee.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@use '../../../../styles/shared-legal.scss'; - diff --git a/src/app/pages/info/guarantee/guarantee.component.ts b/src/app/pages/info/guarantee/guarantee.component.ts deleted file mode 100644 index 0de7ee6..0000000 --- a/src/app/pages/info/guarantee/guarantee.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; -import { LanguageService } from '../../../services/language.service'; -import { GuaranteeRuComponent } from './ru/guarantee-ru.component'; -import { GuaranteeEnComponent } from './en/guarantee-en.component'; -import { GuaranteeHyComponent } from './hy/guarantee-hy.component'; - -@Component({ - selector: 'app-guarantee', - imports: [GuaranteeRuComponent, GuaranteeEnComponent, GuaranteeHyComponent], - templateUrl: './guarantee.component.html', - styleUrls: ['./guarantee.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class GuaranteeComponent { - lang = inject(LanguageService).currentLanguage; -} diff --git a/src/app/pages/info/guarantee/hy/guarantee-hy.component.html b/src/app/pages/info/guarantee/hy/guarantee-hy.component.html deleted file mode 100644 index 97cc9b2..0000000 --- a/src/app/pages/info/guarantee/hy/guarantee-hy.component.html +++ /dev/null @@ -1,158 +0,0 @@ - diff --git a/src/app/pages/info/guarantee/hy/guarantee-hy.component.ts b/src/app/pages/info/guarantee/hy/guarantee-hy.component.ts deleted file mode 100644 index c4b4aa1..0000000 --- a/src/app/pages/info/guarantee/hy/guarantee-hy.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-guarantee-hy', - templateUrl: './guarantee-hy.component.html', - styleUrls: ['../guarantee.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class GuaranteeHyComponent {} diff --git a/src/app/pages/info/guarantee/ru/guarantee-ru.component.html b/src/app/pages/info/guarantee/ru/guarantee-ru.component.html deleted file mode 100644 index da7b8dd..0000000 --- a/src/app/pages/info/guarantee/ru/guarantee-ru.component.html +++ /dev/null @@ -1,158 +0,0 @@ - diff --git a/src/app/pages/info/guarantee/ru/guarantee-ru.component.ts b/src/app/pages/info/guarantee/ru/guarantee-ru.component.ts deleted file mode 100644 index 1175eda..0000000 --- a/src/app/pages/info/guarantee/ru/guarantee-ru.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-guarantee-ru', - templateUrl: './guarantee-ru.component.html', - styleUrls: ['../guarantee.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class GuaranteeRuComponent {} diff --git a/src/app/pages/item-detail/item-detail.component.html b/src/app/pages/item-detail/item-detail.component.html deleted file mode 100644 index 7c90bab..0000000 --- a/src/app/pages/item-detail/item-detail.component.html +++ /dev/null @@ -1,344 +0,0 @@ - -
- @if (loading()) { -
-
-

{{ 'itemDetail.loadingDexar' | translate }}

-
- } - - @if (error()) { - - } - - @if (item() && !loading()) { -
- - - - -
-

{{ getItemName() }}

- - @if (item()!.badges && item()!.badges!.length > 0) { -
- @for (badge of item()!.badges!; track badge) { - {{ badge }} - } -
- } - - @if (item()!.tags && item()!.tags!.length > 0) { -
- @for (tag of item()!.tags!; track tag) { - #{{ tag }} - } -
- } - -
-
- @for (star of [1, 2, 3, 4, 5]; track star) { - - } -
- {{ item()!.rating }} - ({{ item()!.callbacks?.length || 0 }} {{ 'itemDetail.reviewsCount' | translate }}) -
- -
- @if (item()!.discount > 0) { -
- {{ effectivePrice() }} {{ effectiveCurrency() }} - -{{ item()!.discount }}% -
- } -
- {{ item()!.discount > 0 ? (getDiscountedPrice() | number:'1.2-2') : effectivePrice() }} {{ effectiveCurrency() }} -
-
- -
- {{ 'itemDetail.stock' | translate }} - - - {{ getStockLabel() }} - - @if (effectiveRemaining() != null) { - ({{ effectiveRemaining() }} ��.) - } -
- - @if (availableColours().length || availableSizes().length || item()!.colour || (item()!.size && item()!.size!.toLowerCase() !== 'default')) { -
- @if (availableColours().length) { -
- {{ 'itemDetail.colour' | translate }}: - @for (c of availableColours(); track c) { - - } -
- } @else if (item()!.colour) { -
- {{ 'itemDetail.colour' | translate }}: - -
- } - @if (availableSizes().length) { -
- {{ 'itemDetail.size' | translate }}: - @for (s of availableSizes(); track s) { - {{ s }} - } -
- } @else if (item()!.size && item()!.size!.toLowerCase() !== 'default') { -
- {{ 'itemDetail.size' | translate }}: - {{ item()!.size }} -
- } -
- } - - @if (item()!.attributes && item()!.attributes!.length > 0) { -
- @for (attr of item()!.attributes!; track attr.key) { -
- {{ attr.key }} - {{ attr.value }} -
- } -
- } - - @if (hasDeliveryInfo()) { -
-

{{ 'cart.deliveryLabel' | translate }}

- - @if (isDigitalDelivery()) { -
{{ 'cart.digitalDelivery' | translate }}
- } @else { -
- @for (option of deliveryOptions(); track trackByDeliveryOption($index, option)) { -
-
- @if (option.deliveryPlace) { - {{ 'cart.deliveryPlace' | translate }}: {{ option.deliveryPlace }} - } - @if (option.deliveryTime) { - {{ 'cart.deliveryTime' | translate }}: {{ option.deliveryTime }} - } -
- {{ option.deliveryPrice | number:'1.2-2' }} {{ getDeliveryCurrency() }} -
- } -
- } -
- } - - - -
- - @if (false) { -

{{ getSimpleDescription() }}

- } - - @if (hasDescriptionFields()) { -

{{ 'itemDetail.specifications' | translate }}

- - - @for (field of getTranslatedDescriptionFields(); track field.key) { - - - - - } - -
{{ field.key }}{{ field.value }}
- } @else { -

{{ 'itemDetail.description' | translate }}

-
- } -
-
-
- - -
-

{{ 'itemDetail.reviews' | translate }} ({{ item()!.callbacks?.length || 0 }})

- -
-

{{ 'itemDetail.leaveReview' | translate }}

-
- -
- @for (star of [1, 2, 3, 4, 5]; track star) { - - ? - - } -
-
- -
- - @if (!newReview.anonymous && getUserDisplayName()) { - {{ getUserDisplayName() }} - } - -
- - @if (reviewSubmitStatus() === 'success') { -
- - {{ 'itemDetail.reviewSuccess' | translate }} -
- } - - @if (reviewSubmitStatus() === 'error') { -
- - {{ 'itemDetail.reviewError' | translate }} -
- } -
- -
- @if (item()?.callbacks && item()!.callbacks!.length > 0) { - @for (callback of item()!.callbacks; track $index) { -
-
-
- {{ callback.userID ? callback.userID : ('itemDetail.defaultUser' | translate) }} - @if (callback.timestamp) { - {{ formatDate(callback.timestamp) }} - } -
- @if (callback.rating) { -
- @for (star of [1, 2, 3, 4, 5]; track star) { - - } -
- } -
- @if (callback.content) { -

{{ callback.content }}

- } -
- } - } @else { -

{{ 'itemDetail.noReviews' | translate }}

- } -
-
- - - @if (item()!.questions && item()!.questions!.length > 0) { -
-

{{ 'itemDetail.qna' | translate }} ({{ item()!.questions!.length }})

-
- @for (question of item()!.questions!; track $index) { -
-
- - {{ question.question }} -
-
- - {{ question.answer }} -
-
- - -
-
- } -
-
- } - - @if (relatedProducts().length > 0) { - - } - } -
diff --git a/src/app/pages/item-detail/item-detail.component.scss b/src/app/pages/item-detail/item-detail.component.scss deleted file mode 100644 index 044ba12..0000000 --- a/src/app/pages/item-detail/item-detail.component.scss +++ /dev/null @@ -1,2019 +0,0 @@ -@use 'sass:color'; - -// ========== platform ITEM DETAIL - Redesigned 2026 ========== -$dx-font: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; -$dx-dark: #1e3c38; -$dx-primary: #497671; -$dx-secondary: #a1b4b5; -$dx-muted: #697777; -$dx-border: #d3dad9; -$dx-card-bg: #f5f3f9; - -.dx-item-container { - max-width: 1200px; - margin: 0 auto; - padding: 24px; - font-family: $dx-font; -} - -// Loading & Error -.dx-loading, -.dx-error { - text-align: center; - padding: 80px 20px; - color: $dx-muted; - - p { margin: 0; font-size: var(--font-size-lg, 1rem); } -} - -.dx-spinner { - width: 48px; - height: 48px; - border: 4px solid $dx-border; - border-top-color: $dx-primary; - border-radius: 50%; - animation: dxSpin 0.8s linear infinite; - margin: 0 auto 20px; -} - -@keyframes dxSpin { - to { transform: rotate(360deg); } -} - -.dx-back-link { - display: inline-block; - margin-top: 20px; - padding: 12px 28px; - background: $dx-primary; - color: white; - text-decoration: none; - border-radius: var(--radius-lg, 13px); - font-weight: var(--font-weight-semibold, 600); - transition: all 0.2s; - - &:hover { - background: color.adjust($dx-primary, $lightness: -8%); - transform: translateY(-1px); - } -} - -// Main Content Grid -.dx-item-content { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 48px; - margin-bottom: 48px; -} - -.related-products { - margin-top: 48px; - - h2 { - margin: 0 0 20px; - color: $dx-dark; - font-size: var(--font-size-3xl, 1.5rem); - font-weight: 800; - } -} - -.related-products-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); - gap: 24px; -} - -// Gallery: thumbnails left + main photo right -.dx-gallery { - display: flex; - gap: 16px; -} - -.dx-thumbnails { - display: flex; - flex-direction: column; - gap: 10px; - flex-shrink: 0; - width: 80px; - max-height: 520px; - overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: $dx-border transparent; -} - -.dx-thumb { - width: 80px; - height: 80px; - border: 2px solid $dx-border; - border-radius: 10px; - overflow: hidden; - cursor: pointer; - position: relative; - flex-shrink: 0; - transition: all 0.2s; - background: white; - - img { - width: 100%; - height: 100%; - object-fit: contain; - background: white; - padding: 4px; - } - - &:hover { - border-color: $dx-secondary; - } - - &.active { - border-color: $dx-primary; - box-shadow: 0 0 0 1px $dx-primary; - } -} - -.dx-video-badge { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 28px; - height: 28px; - background: rgba(0, 0, 0, 0.6); - color: white; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 10px; - z-index: 1; -} - -.dx-main-photo { - flex: 1; - aspect-ratio: 1; - background: white; - border: 1px solid $dx-border; - border-radius: var(--radius-lg, 13px); - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.08); - - img, video { - width: 100%; - height: 100%; - object-fit: contain; - background: white; - padding: 16px; - } -} - -.dx-no-image { - display: flex; - flex-direction: column; - align-items: center; - gap: 12px; - color: $dx-secondary; - font-size: var(--font-size-md, 0.9375rem); -} - -// Item Info Panel -.dx-info { - display: flex; - flex-direction: column; - gap: 20px; -} - -.dx-title { - font-family: $dx-font; - font-size: var(--font-size-4xl, 2rem); - font-weight: var(--font-weight-bold, 700); - color: $dx-dark; - margin: 0; - line-height: 1.3; -} - -.dx-rating { - display: flex; - align-items: center; - gap: 10px; -} - -.dx-stars { - display: flex; - gap: 3px; -} - -.dx-star--filled svg path { - fill: currentColor; -} - -.dx-rating-value { - font-weight: var(--font-weight-bold, 700); - font-size: var(--font-size-lg, 1rem); - color: $dx-dark; -} - -.dx-rating-count { - font-size: var(--font-size-base, 0.875rem); - color: $dx-muted; -} - -.dx-price-block { - display: flex; - flex-direction: column; - gap: 4px; -} - -.dx-price-row { - display: flex; - align-items: center; - gap: 10px; -} - -.dx-old-price { - text-decoration: line-through; - color: $dx-muted; - font-size: var(--font-size-xl, 1.125rem); -} - -.dx-discount-tag { - background: #ef4444; - color: white; - padding: 3px 10px; - border-radius: 20px; - font-weight: var(--font-weight-bold, 700); - font-size: var(--font-size-sm, 0.8125rem); -} - -.dx-current-price { - font-family: $dx-font; - font-size: 2.25rem; - font-weight: 800; - color: $dx-primary; - line-height: 1.1; -} - -.dx-stock { - display: flex; - align-items: center; - gap: 10px; -} - -.dx-stock-label { - font-size: var(--font-size-md, 0.9375rem); - color: $dx-muted; -} - -.dx-stock-status { - display: flex; - align-items: center; - gap: 6px; - font-size: var(--font-size-md, 0.9375rem); - font-weight: var(--font-weight-semibold, 600); - - &.high { color: #2ed573; } - &.medium { color: #ffa502; } - &.low { color: #ef4444; } -} - -.dx-stock-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: currentColor; -} - -.dx-add-cart { - width: 100%; - max-width: 400px; - padding: 16px 24px; - background: $dx-primary; - color: white; - border: none; - border-radius: var(--radius-lg, 13px); - font-family: $dx-font; - font-size: var(--font-size-xl, 1.125rem); - font-weight: var(--font-weight-bold, 700); - cursor: pointer; - transition: all 0.2s; - display: flex; - align-items: center; - justify-content: center; - gap: 10px; - box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.15); - - &:hover { - background: color.adjust($dx-primary, $lightness: -8%); - transform: translateY(-2px); - box-shadow: 0 6px 16px rgba(73, 118, 113, 0.3); - } - - &:active { - transform: translateY(0); - } -} - -// Variant chips (colour/size) � shared between platform and alt -.dx-variants, .alt-variants { - display: flex; - flex-direction: column; - gap: 12px; - margin-bottom: 12px; - - .variant-group { - display: flex; - align-items: center; - gap: 8px; - flex-wrap: wrap; - } - - .variant-label { - font-size: var(--font-size-base, 0.875rem); - color: #6b7280; - font-weight: var(--font-weight-medium, 500); - } - - .variant-chip { - display: inline-flex; - align-items: center; - padding: 6px 14px; - border-radius: var(--radius-sm, 8px); - font-size: var(--font-size-base, 0.875rem); - font-weight: var(--font-weight-semibold, 600); - border: 1.5px solid $dx-border; - background: rgba(73, 118, 113, 0.06); - color: $dx-primary; - cursor: pointer; - transition: background 0.15s, border-color 0.15s, box-shadow 0.15s; - - &:hover { - background: rgba(73, 118, 113, 0.12); - } - - &.active { - border-color: $dx-primary; - background: rgba(73, 118, 113, 0.18); - box-shadow: 0 0 0 2px rgba(73, 118, 113, 0.25); - } - } - - .colour-swatch { - width: 32px; - height: 32px; - border-radius: 50%; - border: 2px solid $dx-border; - cursor: pointer; - transition: border-color 0.15s, box-shadow 0.15s; - flex-shrink: 0; - - &:hover { - border-color: $dx-primary; - } - - &.active { - border-color: $dx-primary; - box-shadow: 0 0 0 2px rgba(73, 118, 113, 0.25); - } - } -} - -.dx-attributes, .alt-attributes { - display: flex; - flex-wrap: wrap; - gap: 6px 16px; - margin-bottom: 14px; - padding: 10px 14px; - background: #f8fafa; - border-radius: 10px; - - .attribute-row { - display: flex; - gap: 6px; - font-size: var(--font-size-sm, 0.8125rem); - } - - .attribute-key { - color: #6b7280; - &::after { content: ':'; } - } - - .attribute-value { - font-weight: var(--font-weight-semibold, 600); - color: #1a1a1a; - } -} - -.dx-delivery, .alt-delivery { - display: flex; - flex-direction: column; - gap: 10px; - padding: 14px 16px; - border: 1px solid $dx-border; - border-radius: var(--radius-md, 12px); - background: #f8fafa; -} - -.dx-delivery-title, .alt-delivery-title { - margin: 0; - font-size: var(--font-size-lg, 1rem); - font-weight: var(--font-weight-bold, 700); - color: $dx-dark; -} - -.dx-delivery-list, .alt-delivery-list { - display: flex; - flex-direction: column; - gap: 8px; -} - -.dx-delivery-option, .alt-delivery-option { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 12px; - padding: 10px 12px; - border-radius: 10px; - background: white; -} - -.dx-delivery-option-main, .alt-delivery-option-main { - display: flex; - flex-direction: column; - gap: 4px; - color: $dx-muted; - font-size: var(--font-size-base, 0.875rem); -} - -.dx-delivery-price, .alt-delivery-price { - flex-shrink: 0; - font-weight: var(--font-weight-bold, 700); - color: $dx-primary; - font-size: var(--font-size-md, 0.9375rem); -} - -.dx-delivery-chip, .alt-delivery-chip { - display: inline-flex; - align-self: flex-start; - padding: 6px 12px; - border-radius: var(--radius-full, 999px); - background: rgba(73, 118, 113, 0.12); - color: $dx-primary; - font-size: var(--font-size-base, 0.875rem); - font-weight: var(--font-weight-bold, 700); -} - -.dx-description { - padding-top: 8px; - border-top: 1px solid $dx-border; - - h2 { - font-family: $dx-font; - font-size: var(--font-size-3xl, 1.5rem); - font-weight: var(--font-weight-bold, 700); - color: $dx-dark; - margin: 0 0 12px 0; - } -} - -.dx-description-text { - font-size: var(--font-size-md, 0.9375rem); - color: $dx-muted; - line-height: 1.7; -} - -// Reviews Section -.dx-reviews-section { - margin-bottom: 48px; - - h2 { - font-family: $dx-font; - font-size: var(--font-size-3xl, 1.5rem); - font-weight: var(--font-weight-bold, 700); - color: $dx-dark; - margin: 0 0 24px 0; - } -} - -.dx-review-form { - background: $dx-card-bg; - border: 1px solid $dx-border; - border-radius: var(--radius-lg, 13px); - padding: 24px; - margin-bottom: 24px; - - h3 { - font-family: $dx-font; - margin: 0 0 20px 0; - color: $dx-dark; - font-size: var(--font-size-xl, 1.125rem); - font-weight: var(--font-weight-bold, 700); - } -} - -.dx-rating-input { - margin-bottom: 16px; - - label { - display: block; - margin-bottom: 8px; - color: $dx-dark; - font-weight: var(--font-weight-semibold, 600); - font-size: var(--font-size-md, 0.9375rem); - } -} - -.dx-star-selector { - display: flex; - gap: 6px; - - .dx-star-pick { - font-size: 1.8rem; - cursor: pointer; - opacity: 0.25; - transition: all 0.15s; - user-select: none; - - &.selected { opacity: 1; } - &:hover { opacity: 0.7; transform: scale(1.15); } - } -} - -.dx-textarea { - width: 100%; - padding: 14px; - border: 1px solid $dx-border; - border-radius: 10px; - font-family: $dx-font; - font-size: var(--font-size-md, 0.9375rem); - resize: none; - margin-bottom: 16px; - background: white; - transition: border-color 0.2s; - - &:focus { - outline: none; - border-color: $dx-primary; - box-shadow: 0 0 0 3px rgba(73, 118, 113, 0.1); - } -} - -.dx-form-actions { - display: flex; - align-items: center; - gap: 16px; - flex-wrap: wrap; -} - -.dx-anon-toggle { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-size: var(--font-size-base, 0.875rem); - color: $dx-muted; - - input[type="checkbox"] { - width: 18px; - height: 18px; - accent-color: $dx-primary; - cursor: pointer; - } -} - -.dx-user-preview { - color: $dx-primary; - font-size: var(--font-size-sm, 0.8125rem); - font-style: italic; -} - -.dx-submit-btn { - margin-left: auto; - background: $dx-primary; - color: white; - border: none; - padding: 12px 28px; - border-radius: var(--radius-lg, 13px); - font-family: $dx-font; - font-weight: var(--font-weight-bold, 700); - font-size: var(--font-size-md, 0.9375rem); - cursor: pointer; - transition: all 0.2s; - display: inline-flex; - align-items: center; - gap: 8px; - min-width: 140px; - justify-content: center; - - &:hover:not(:disabled) { - background: color.adjust($dx-primary, $lightness: -8%); - transform: translateY(-1px); - } - - &:disabled { - background: $dx-border; - color: $dx-muted; - cursor: not-allowed; - } - - &.submitting { - background: $dx-secondary; - } -} - -.dx-spinner-sm { - width: 16px; - height: 16px; - border: 2px solid rgba(255, 255, 255, 0.3); - border-top-color: white; - border-radius: 50%; - animation: dxSpin 0.8s linear infinite; -} - -.dx-status { - margin-top: 16px; - padding: 12px 16px; - border-radius: 10px; - display: flex; - align-items: center; - gap: 10px; - font-weight: var(--font-weight-semibold, 600); - font-size: var(--font-size-md, 0.9375rem); - animation: dxSlideIn 0.3s ease-out; - - &.success { - background: #d1fae5; - color: #065f46; - border: 1px solid #6ee7b7; - } - - &.error { - background: #fee2e2; - color: #991b1b; - border: 1px solid #fca5a5; - } -} - -@keyframes dxSlideIn { - from { opacity: 0; transform: translateY(-8px); } - to { opacity: 1; transform: translateY(0); } -} - -// Reviews List -.dx-reviews-list { - display: flex; - flex-direction: column; - gap: 16px; -} - -.dx-review-card { - background: white; - border: 1px solid $dx-border; - border-radius: var(--radius-lg, 13px); - padding: 20px; - transition: box-shadow 0.2s; - - &:hover { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06); - } -} - -.dx-review-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; -} - -.dx-reviewer { - display: flex; - flex-direction: column; - gap: 2px; -} - -.dx-reviewer-name { - font-weight: var(--font-weight-semibold, 600); - color: $dx-dark; - font-size: var(--font-size-md, 0.9375rem); -} - -.dx-review-date { - color: $dx-muted; - font-size: var(--font-size-sm, 0.8125rem); -} - -.dx-review-stars { - display: flex; - gap: 2px; -} - -.dx-review-text { - color: $dx-muted; - line-height: 1.6; - margin: 0; - font-size: var(--font-size-md, 0.9375rem); -} - -.dx-no-reviews { - text-align: center; - color: $dx-muted; - padding: 40px; - font-size: var(--font-size-lg, 1rem); -} - -// Q&A Section -.dx-qa-section { - margin-bottom: 48px; - - h2 { - font-family: $dx-font; - font-size: var(--font-size-3xl, 1.5rem); - font-weight: var(--font-weight-bold, 700); - color: $dx-dark; - margin: 0 0 24px 0; - } -} - -.dx-qa-list { - display: flex; - flex-direction: column; - gap: 16px; -} - -.dx-qa-card { - background: white; - border: 1px solid $dx-border; - border-radius: var(--radius-lg, 13px); - padding: 20px; -} - -.dx-question, -.dx-answer { - display: flex; - gap: 10px; - margin-bottom: 12px; - line-height: 1.6; - color: $dx-muted; - font-size: var(--font-size-md, 0.9375rem); -} - -.dx-qa-label { - flex-shrink: 0; - width: 28px; - height: 28px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-weight: var(--font-weight-bold, 700); - font-size: var(--font-size-sm, 0.8125rem); - - &.q { - background: $dx-primary; - color: white; - } - - &.a { - background: $dx-card-bg; - color: $dx-primary; - border: 1px solid $dx-border; - } -} - -.dx-qa-votes { - display: flex; - gap: 12px; - padding-top: 8px; - border-top: 1px solid $dx-border; -} - -.dx-vote { - display: flex; - align-items: center; - gap: 5px; - background: none; - border: 1px solid $dx-border; - border-radius: var(--radius-sm, 8px); - padding: 6px 12px; - font-size: var(--font-size-sm, 0.8125rem); - color: $dx-muted; - cursor: pointer; - transition: all 0.2s; - font-family: $dx-font; - - &.up:hover { - border-color: #2ed573; - color: #2ed573; - background: rgba(46, 213, 115, 0.05); - } - - &.down:hover { - border-color: #ef4444; - color: #ef4444; - background: rgba(239, 68, 68, 0.05); - } -} - -// ========== platform RESPONSIVE ========== - -// Large desktop � constrain gallery height -@media (min-width: 1201px) { - .dx-main-photo { - max-height: 560px; - } -} - -// Tablet landscape / small desktop -@media (max-width: 1200px) { - .dx-item-content { - gap: 32px; - } - - .dx-title { - font-size: var(--font-size-3xl, 1.5rem); - } -} - -// Tablet portrait -@media (max-width: 992px) { - .dx-item-content { - grid-template-columns: 1fr; - gap: 32px; - } - - .dx-gallery { - max-width: 600px; - margin: 0 auto; - } - - .dx-main-photo { - max-height: 480px; - aspect-ratio: auto; - } - - .dx-add-cart { - max-width: 100%; - } - - .dx-reviews-section, - .dx-qa-section { - h2 { - font-size: var(--font-size-3xl, 1.5rem); - } - } -} - -// Mobile -@media (max-width: 768px) { - .dx-item-container { - padding: 16px; - } - - .dx-item-content { - gap: 24px; - margin-bottom: 32px; - } - - // Thumbnails go below main photo - .dx-gallery { - flex-direction: column; - max-width: 100%; - } - - .dx-thumbnails { - flex-direction: row; - width: 100%; - max-height: none; - overflow-x: auto; - overflow-y: hidden; - order: 1; - - scrollbar-width: none; - &::-webkit-scrollbar { display: none; } - } - - .dx-main-photo { - order: 0; - max-height: 400px; - aspect-ratio: auto; - } - - .dx-thumb { - width: 64px; - height: 64px; - min-width: 64px; - } - - .dx-title { - font-size: var(--font-size-3xl, 1.5rem); - } - - .dx-current-price { - font-size: 1.8rem; - } - - .dx-old-price { - font-size: var(--font-size-lg, 1rem); - } - - .dx-add-cart { - max-width: 100%; - padding: 14px 20px; - font-size: var(--font-size-lg, 1rem); - } - - .dx-description { - h2 { - font-size: var(--font-size-xl, 1.125rem); - } - } - - .dx-specs-table { - .spec-key { - white-space: normal; - width: auto; - } - - td { - padding: 8px 10px; - font-size: var(--font-size-sm, 0.8125rem); - } - } - - .dx-review-form { - padding: 16px; - } - - .dx-form-actions { - flex-direction: column; - align-items: stretch; - - .dx-submit-btn { - margin-left: 0; - width: 100%; - } - } - - .dx-review-card { - padding: 16px; - } - - .dx-reviews-section, - .dx-qa-section { - margin-bottom: 32px; - - h2 { - font-size: var(--font-size-2xl, 1.25rem); - margin-bottom: 16px; - } - } - - .dx-qa-card { - padding: 16px; - } - - .dx-question, - .dx-answer { - font-size: var(--font-size-base, 0.875rem); - } -} - -// Small mobile -@media (max-width: 480px) { - .dx-item-container { - padding: 12px; - } - - .dx-item-content { - gap: 20px; - margin-bottom: 24px; - } - - .dx-main-photo { - max-height: 300px; - border-radius: 10px; - - img, video { - padding: 8px; - } - } - - .dx-thumb { - width: 52px; - height: 52px; - min-width: 52px; - } - - .dx-title { - font-size: var(--font-size-2xl, 1.25rem); - } - - .dx-info { - gap: 16px; - } - - .dx-current-price { - font-size: var(--font-size-3xl, 1.5rem); - } - - .dx-rating { - flex-wrap: wrap; - gap: 6px; - } - - .dx-stock { - flex-wrap: wrap; - gap: 6px; - } - - .dx-add-cart { - padding: 12px 16px; - font-size: var(--font-size-md, 0.9375rem); - border-radius: 10px; - } - - .dx-review-form { - padding: 14px; - - h3 { - font-size: var(--font-size-lg, 1rem); - } - } - - .dx-star-selector { - .dx-star-pick { - font-size: var(--font-size-3xl, 1.5rem); - } - } - - .dx-textarea { - padding: 12px; - font-size: var(--font-size-base, 0.875rem); - } - - .dx-review-card { - padding: 14px; - } - - .dx-reviewer-name { - font-size: var(--font-size-base, 0.875rem); - } - - .dx-review-text { - font-size: var(--font-size-base, 0.875rem); - } - - .dx-specs-table { - td { - padding: 6px 8px; - font-size: var(--font-size-sm, 0.8125rem); - display: block; - } - - .spec-key { - width: 100%; - padding-bottom: 2px; - } - - .spec-value { - padding-top: 0; - } - - tr { - display: flex; - flex-direction: column; - padding: 6px 0; - } - } - - .dx-qa-card { - padding: 14px; - } - - .dx-question, - .dx-answer { - font-size: var(--font-size-sm, 0.8125rem); - gap: 8px; - } - - .dx-qa-label { - width: 24px; - height: 24px; - font-size: var(--font-size-xs, 0.75rem); - } -} - -// ========== alt ITEM DETAIL STYLES ========== -.alt-item-container { - max-width: 1400px; - margin: 0 auto; - padding: 2rem; -} - -.alt-loading, -.alt-error { - text-align: center; - padding: 4rem 1rem; -} - -.alt-spinner { - width: 48px; - height: 48px; - border: 3px solid var(--border-color); - border-top-color: var(--primary-color); - border-radius: 50%; - animation: spin 0.8s linear infinite; - margin: 0 auto 1.5rem; -} - -.alt-back-link { - display: inline-block; - margin-top: 1.5rem; - padding: 0.75rem 2rem; - background: var(--primary-color); - color: white; - text-decoration: none; - border-radius: var(--radius-lg); - font-weight: var(--font-weight-semibold, 600); - transition: all 0.3s; - - &:hover { - background: var(--primary-hover); - transform: translateY(-2px); - box-shadow: var(--shadow-lg); - } -} - -.alt-item-content { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 3rem; - margin-bottom: 3rem; -} - -.alt-gallery { - .alt-main-photo { - width: 100%; - aspect-ratio: 1; - background: var(--bg-secondary); - border-radius: var(--radius-xl); - overflow: hidden; - margin-bottom: 1rem; - border: 1px solid var(--border-color); - - img, - video { - width: 100%; - height: 100%; - object-fit: contain; - background: white; - } - - &.alt-no-image { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - color: var(--text-tertiary); - - svg { - margin-bottom: 1rem; - stroke-width: 1; - } - - p { - font-size: var(--font-size-base, 0.875rem); - } - } - } - - .alt-thumbnails { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)); - gap: 0.75rem; - } - - .alt-thumb { - aspect-ratio: 1; - background: var(--bg-secondary); - border-radius: var(--radius-md); - overflow: hidden; - cursor: pointer; - border: 2px solid transparent; - transition: all 0.2s; - position: relative; - - &:hover { - border-color: var(--primary-color); - transform: scale(1.05); - } - - &.active { - border-color: var(--primary-color); - box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1); - } - - img { - width: 100%; - height: 100%; - object-fit: contain; - background: white; - } - - .video-indicator { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: rgba(0, 0, 0, 0.7); - color: white; - width: 28px; - height: 28px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 10px; - padding-left: 2px; - } - } -} - -.alt-info { - .alt-title { - font-size: var(--font-size-4xl, 2rem); - font-weight: var(--font-weight-bold, 700); - color: var(--text-primary); - margin: 0 0 1rem 0; - line-height: 1.3; - } - - .alt-rating { - display: flex; - align-items: center; - gap: 0.75rem; - margin-bottom: 1.5rem; - - .stars { - color: #fbbf24; - font-size: var(--font-size-xl, 1.125rem); - } - - .value { - font-weight: var(--font-weight-bold, 700); - color: var(--text-primary); - } - - .reviews { - color: var(--text-secondary); - font-size: var(--font-size-base, 0.875rem); - } - } - - .alt-price-block { - background: var(--bg-secondary); - padding: 1.5rem; - border-radius: var(--radius-lg); - margin-bottom: 1.5rem; - - .price-row { - display: flex; - align-items: center; - gap: 0.75rem; - margin-bottom: 0.5rem; - } - - .old-price { - text-decoration: line-through; - color: var(--text-tertiary); - font-size: var(--font-size-xl, 1.125rem); - } - - .discount-badge { - background: #ef4444; - color: white; - padding: 0.25rem 0.75rem; - border-radius: var(--radius-md); - font-weight: var(--font-weight-bold, 700); - font-size: var(--font-size-sm, 0.8125rem); - } - - .current-price { - font-size: 2.5rem; - font-weight: 800; - color: var(--primary-color); - line-height: 1; - } - } - - .alt-stock { - display: flex; - align-items: center; - gap: 1rem; - margin-bottom: 1.5rem; - padding: 1rem; - background: white; - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - - .stock-label { - font-weight: var(--font-weight-semibold, 600); - color: var(--text-secondary); - } - - .stock-indicator { - display: flex; - align-items: center; - gap: 0.5rem; - font-weight: var(--font-weight-semibold, 600); - - .dot { - width: 10px; - height: 10px; - border-radius: 50%; - display: block; - } - - &.high { - color: #10b981; - .dot { background: #10b981; } - } - - &.medium { - color: #f59e0b; - .dot { background: #f59e0b; } - } - - &.low { - color: #ef4444; - .dot { background: #ef4444; } - } - } - } - - .alt-add-cart { - width: 100%; - padding: 1.25rem 2rem; - background: var(--primary-color); - color: white; - border: none; - border-radius: var(--radius-lg); - font-size: var(--font-size-xl, 1.125rem); - font-weight: var(--font-weight-bold, 700); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - gap: 0.75rem; - transition: all 0.3s; - margin-bottom: 2rem; - - &:hover { - background: var(--primary-hover); - transform: translateY(-2px); - box-shadow: var(--shadow-lg); - } - - &:active { - transform: translateY(0); - } - } - - .alt-description { - h3 { - font-size: var(--font-size-2xl, 1.25rem); - font-weight: var(--font-weight-semibold, 600); - color: var(--text-primary); - margin: 0 0 1rem 0; - } - - ::ng-deep { - p { color: var(--text-secondary); line-height: 1.7; margin-bottom: 1rem; } - ul, ol { padding-left: 1.5rem; margin-bottom: 1rem; } - li { margin-bottom: 0.5rem; color: var(--text-secondary); } - strong { color: var(--text-primary); } - } - } -} - -.alt-reviews { - margin-top: 3rem; - padding-top: 3rem; - border-top: 1px solid var(--border-color); - - h2 { - font-size: var(--font-size-4xl, 2rem); - font-weight: var(--font-weight-bold, 700); - color: var(--text-primary); - margin: 0 0 2rem 0; - } - - .alt-reviews-list { - display: grid; - gap: 1.5rem; - } - - .alt-review-card { - background: white; - border: 1px solid var(--border-color); - border-radius: var(--radius-lg); - padding: 1.5rem; - transition: all 0.3s; - - &:hover { - box-shadow: var(--shadow-md); - transform: translateY(-2px); - border-color: #10b981; - } - - .review-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 1rem; - gap: 1rem; - - .reviewer-info { - display: flex; - flex-direction: column; - gap: 0.25rem; - - .reviewer-name { - font-weight: var(--font-weight-semibold, 600); - font-size: var(--font-size-lg, 1rem); - color: var(--text-primary); - } - - .review-date { - font-size: var(--font-size-sm, 0.8125rem); - color: var(--text-secondary); - } - } - - .review-stars { - color: #fbbf24; - font-size: var(--font-size-xl, 1.125rem); - flex-shrink: 0; - } - } - - .review-text { - color: var(--text-secondary); - line-height: 1.7; - margin: 0; - font-size: var(--font-size-md, 0.9375rem); - } - } - - .alt-no-reviews { - text-align: center; - padding: 3rem 2rem; - color: var(--text-secondary); - font-size: var(--font-size-lg, 1rem); - background: var(--bg-secondary); - border-radius: var(--radius-lg); - } -} - -/* alt Review Form */ -.alt-review-form { - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: var(--radius-lg); - padding: 2rem; - margin-bottom: 2rem; - - h3 { - font-size: var(--font-size-2xl, 1.25rem); - font-weight: var(--font-weight-bold, 700); - color: var(--text-primary); - margin: 0 0 1.5rem 0; - } - - .alt-rating-input { - margin-bottom: 1.5rem; - - label { - display: block; - font-size: var(--font-size-md, 0.9375rem); - font-weight: var(--font-weight-semibold, 600); - color: var(--text-primary); - margin-bottom: 0.75rem; - } - - .alt-star-selector { - display: flex; - gap: 0.5rem; - - .alt-star { - font-size: var(--font-size-4xl, 2rem); - color: #d1d5db; - cursor: pointer; - transition: all 0.2s ease; - user-select: none; - - &:hover, &.selected { - color: #fbbf24; - transform: scale(1.1); - } - - &.selected { - text-shadow: 0 0 10px rgba(251, 191, 36, 0.5); - } - } - } - } - - .alt-textarea { - width: 100%; - padding: 1rem; - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - font-size: var(--font-size-md, 0.9375rem); - font-family: inherit; - color: var(--text-primary); - background: white; - resize: none; - min-height: 100px; - transition: all 0.2s ease; - - &:focus { - outline: none; - border-color: #10b981; - box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1); - } - - &::placeholder { - color: var(--text-secondary); - opacity: 0.6; - } - } - - .alt-form-actions { - display: flex; - align-items: center; - gap: 1rem; - margin-top: 1rem; - flex-wrap: wrap; - - .alt-anonymous-toggle { - display: flex; - align-items: center; - gap: 0.5rem; - font-size: var(--font-size-base, 0.875rem); - color: var(--text-secondary); - cursor: pointer; - - input[type="checkbox"] { - cursor: pointer; - width: 18px; - height: 18px; - } - - span { - user-select: none; - } - } - - .alt-username-preview { - font-size: var(--font-size-base, 0.875rem); - color: var(--text-secondary); - font-style: italic; - } - - .alt-submit-review-btn { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1.5rem; - margin-left: auto; - background: linear-gradient(135deg, #10b981, #14b8a6, #06b6d4); - color: white; - border: none; - border-radius: var(--radius-md); - font-size: var(--font-size-md, 0.9375rem); - font-weight: var(--font-weight-semibold, 600); - cursor: pointer; - transition: all 0.3s ease; - - svg { - width: 18px; - height: 18px; - } - - &:hover:not(:disabled) { - transform: translateY(-2px); - box-shadow: 0 8px 20px rgba(16, 185, 129, 0.3); - } - - &:disabled { - opacity: 0.5; - cursor: not-allowed; - transform: none; - } - - &.submitting { - pointer-events: none; - } - - .alt-spinner-small { - width: 16px; - height: 16px; - border: 2px solid rgba(255, 255, 255, 0.3); - border-top-color: white; - border-radius: 50%; - animation: spin 0.8s linear infinite; - } - } - } - - .alt-status-message { - display: flex; - align-items: center; - gap: 0.75rem; - padding: 1rem; - margin-top: 1rem; - border-radius: var(--radius-md); - font-size: var(--font-size-md, 0.9375rem); - font-weight: var(--font-weight-medium, 500); - animation: slideDown 0.3s ease; - - svg { - flex-shrink: 0; - width: 20px; - height: 20px; - } - - &.success { - background: rgba(16, 185, 129, 0.1); - color: #059669; - border: 1px solid rgba(16, 185, 129, 0.3); - - svg { - stroke: #059669; - } - } - - &.error { - background: rgba(239, 68, 68, 0.1); - color: #dc2626; - border: 1px solid rgba(239, 68, 68, 0.3); - - svg { - stroke: #dc2626; - } - } - } -} - -@keyframes slideDown { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -// ========== alt RESPONSIVE ========== - -// Tablet portrait -@media (max-width: 968px) { - .alt-item-content { - grid-template-columns: 1fr; - gap: 2rem; - } - - .alt-gallery { - max-width: 600px; - margin: 0 auto; - } - - .alt-info .alt-title { - font-size: var(--font-size-3xl, 1.5rem); - } - - .alt-info .alt-price-block .current-price { - font-size: var(--font-size-4xl, 2rem); - } - - .alt-info .alt-add-cart { - max-width: 100%; - } - - .alt-review-form { - padding: 1.5rem; - - .alt-form-actions { - flex-direction: column; - align-items: stretch; - - .alt-submit-review-btn { - margin-left: 0; - justify-content: center; - } - } - } -} - -// Mobile -@media (max-width: 768px) { - .alt-item-container { - padding: 1rem; - } - - .alt-item-content { - gap: 1.5rem; - margin-bottom: 2rem; - } - - .alt-gallery { - max-width: 100%; - - .alt-main-photo { - border-radius: var(--radius-lg); - margin-bottom: 0.75rem; - } - - .alt-thumbnails { - grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); - gap: 0.5rem; - } - } - - .alt-info { - .alt-title { - font-size: var(--font-size-3xl, 1.5rem); - } - - .alt-rating { - flex-wrap: wrap; - gap: 0.5rem; - margin-bottom: 1rem; - } - - .alt-price-block { - padding: 1rem; - - .current-price { - font-size: var(--font-size-4xl, 2rem); - } - - .old-price { - font-size: var(--font-size-md, 0.9375rem); - } - } - - .alt-stock { - flex-wrap: wrap; - gap: 0.5rem; - padding: 0.75rem; - } - - .alt-add-cart { - padding: 1rem 1.5rem; - font-size: var(--font-size-lg, 1rem); - margin-bottom: 1.5rem; - } - - .alt-description { - h3 { - font-size: var(--font-size-xl, 1.125rem); - } - } - } - - .alt-specs-table { - .spec-key { - white-space: normal; - width: auto; - } - - td { - padding: 8px 10px; - font-size: var(--font-size-sm, 0.8125rem); - } - } - - .alt-reviews { - margin-top: 2rem; - padding-top: 2rem; - - h2 { - font-size: var(--font-size-3xl, 1.5rem); - margin-bottom: 1.5rem; - } - } - - .alt-review-card { - padding: 1rem; - } - - .alt-review-form { - padding: 1.25rem; - - h3 { - font-size: var(--font-size-xl, 1.125rem); - } - } -} - -// Small mobile -@media (max-width: 480px) { - .alt-item-container { - padding: 0.75rem; - } - - .alt-item-content { - gap: 1.25rem; - margin-bottom: 1.5rem; - } - - .alt-gallery { - .alt-thumbnails { - grid-template-columns: repeat(auto-fill, minmax(52px, 1fr)); - gap: 0.4rem; - } - - .alt-main-photo { - border-radius: var(--radius-md); - } - } - - .alt-info { - .alt-title { - font-size: var(--font-size-xl, 1.125rem); - } - - .alt-price-block { - padding: 0.75rem; - - .current-price { - font-size: var(--font-size-3xl, 1.5rem); - } - } - - .alt-stock { - padding: 0.6rem; - font-size: var(--font-size-sm, 0.8125rem); - } - - .alt-add-cart { - padding: 0.85rem 1rem; - font-size: var(--font-size-md, 0.9375rem); - border-radius: var(--radius-md); - } - } - - .alt-review-form { - padding: 1rem; - - .alt-rating-input { - .alt-star-selector { - .alt-star { - font-size: 1.6rem; - } - } - } - - .alt-textarea { - padding: 0.75rem; - font-size: var(--font-size-base, 0.875rem); - } - } - - .alt-review-card { - padding: 0.75rem; - - .review-header { - flex-direction: column; - gap: 0.5rem; - - .review-stars { - align-self: flex-start; - } - } - - .review-text { - font-size: var(--font-size-base, 0.875rem); - } - } - - .alt-specs-table { - td { - padding: 6px 8px; - font-size: var(--font-size-sm, 0.8125rem); - display: block; - } - - .spec-key { - width: 100%; - padding-bottom: 2px; - } - - .spec-value { - padding-top: 0; - } - - tr { - display: flex; - flex-direction: column; - padding: 6px 0; - } - } -} - -// ========== BADGES, TAGS & SPECS (shared) ========== - -// Badges -.alt-badges, .dx-badges { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin: 8px 0; -} - -.item-badge { - display: inline-block; - padding: 3px 10px; - border-radius: var(--radius-xs, 4px); - font-size: var(--font-size-xs, 0.75rem); - font-weight: var(--font-weight-semibold, 600); - text-transform: uppercase; - letter-spacing: 0.5px; - color: #fff; - - &.badge-new { background: #4caf50; } - &.badge-sale { background: #f44336; } - &.badge-exclusive { background: #9c27b0; } - &.badge-hot { background: #ff5722; } - &.badge-limited { background: #ff9800; } - &.badge-bestseller { background: #2196f3; } - &.badge-featured { background: #607d8b; } - &.badge-custom { background: #78909c; } -} - -// Tags -.alt-tags, .dx-tags { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin: 6px 0 12px; -} - -.item-tag { - display: inline-block; - padding: 2px 8px; - border-radius: var(--radius-md, 12px); - font-size: var(--font-size-xs, 0.75rem); - color: #497671; - background: rgba(73, 118, 113, 0.08); - border: 1px solid rgba(73, 118, 113, 0.15); -} - -// Specs table -.alt-specs-table, .dx-specs-table { - width: 100%; - border-collapse: collapse; - margin: 12px 0; - - tr { - border-bottom: 1px solid #e8ecec; - &:last-child { border-bottom: none; } - } - - td { - padding: 10px 12px; - font-size: var(--font-size-base, 0.875rem); - vertical-align: top; - } - - .spec-key { - color: #697777; - font-weight: var(--font-weight-medium, 500); - width: 40%; - white-space: nowrap; - } - - .spec-value { - color: #1e3c38; - } -} - -// Simple description -.alt-simple-desc, .dx-simple-desc { - font-size: var(--font-size-md, 0.9375rem); - color: #697777; - line-height: 1.6; - margin-bottom: 16px; -} - -// Stock quantity -.stock-qty, .dx-stock-qty { - font-size: var(--font-size-sm, 0.8125rem); - color: #697777; - margin-left: 8px; -} - diff --git a/src/app/pages/item-detail/item-detail.component.ts b/src/app/pages/item-detail/item-detail.component.ts deleted file mode 100644 index 1ebc44a..0000000 --- a/src/app/pages/item-detail/item-detail.component.ts +++ /dev/null @@ -1,369 +0,0 @@ -import { Component, OnInit, OnDestroy, signal, computed, ChangeDetectionStrategy, inject } from '@angular/core'; -import { DecimalPipe } from '@angular/common'; -import { FormsModule } from '@angular/forms'; -import { ActivatedRoute, RouterLink } from '@angular/router'; -import { ApiService, CartService, TelegramService, LanguageService, SeoService } from '../../services'; -import { AuthService } from '../../services/auth.service'; -import { Item, ItemDetail, DescriptionField, DeliveryOption } from '../../models'; -import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; -import { Subscription } from 'rxjs'; -import { SecurityContext } from '@angular/core'; -import { getStockStatus, getBadgeClass, getTranslatedField } from '../../utils/item.utils'; -import { LangRoutePipe } from '../../pipes/lang-route.pipe'; -import { TranslatePipe } from '../../i18n/translate.pipe'; -import { TranslateService } from '../../i18n/translate.service'; -import { ProductFacade } from '../../facades/platform/product.facade'; -import { ProductCardComponent } from '../../components/product-card/product-card.component'; -import { IconComponent } from '../../shared/ui/icon/icon.component'; - -@Component({ - selector: 'app-item-detail', - imports: [DecimalPipe, RouterLink, FormsModule, LangRoutePipe, TranslatePipe, ProductCardComponent, IconComponent], - templateUrl: './item-detail.component.html', - styleUrls: ['./item-detail.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ItemDetailComponent implements OnInit, OnDestroy { - item = signal(null); - selectedPhotoIndex = signal(0); - relatedProducts = signal([]); - loading = signal(true); - error = signal(null); - - // Variant selection - selectedColour = signal(null); - selectedSize = signal(null); - - availableColours = computed(() => { - const details = this.item()?.itemDetails; - if (!details?.length) return [] as string[]; - const unique = [...new Set(details.map(d => d.colour || d.color).filter((c): c is string => !!c))]; - return unique; - }); - - availableSizes = computed(() => { - const details = this.item()?.itemDetails; - if (!details?.length) return [] as string[]; - // If a colour is selected, only show sizes available for that colour - const colour = this.selectedColour(); - const filtered = colour - ? details.filter(d => (d.colour || d.color) === colour) - : details; - const unique = [...new Set(filtered.map(d => d.size).filter((s): s is string => !!s && s.toLowerCase() !== 'default'))]; - return unique; - }); - - selectedDetail = computed(() => { - const details = this.item()?.itemDetails; - if (!details?.length) return null; - const colour = this.selectedColour(); - const size = this.selectedSize(); - return details.find(d => - (!colour || (d.colour || d.color) === colour) && - (!size || d.size === size) - ) ?? null; - }); - - effectivePrice = computed(() => { - const detail = this.selectedDetail(); - return detail?.price ?? this.item()?.price ?? 0; - }); - - effectiveCurrency = computed(() => { - const detail = this.selectedDetail(); - return detail?.currency ?? this.item()?.currency ?? ''; - }); - - effectiveRemaining = computed(() => { - const detail = this.selectedDetail(); - return detail?.remaining ?? this.item()?.quantity ?? null; - }); - - deliveryOptions = computed(() => this.item()?.deliveryOptions ?? []); - - hasDeliveryInfo = computed(() => { - const currentItem = this.item(); - return currentItem?.deliveryMode === 'digital' || this.deliveryOptions().length > 0; - }); - - newReview = { - rating: 0, - comment: '', - anonymous: false - }; - - reviewSubmitStatus = signal<'idle' | 'loading' | 'success' | 'error'>('idle'); - - private routeSubscription?: Subscription; - private reviewResetTimeout?: ReturnType; - private reviewErrorTimeout?: ReturnType; - private reloadTimeout?: ReturnType; - - private seoService = inject(SeoService); - private i18n = inject(TranslateService); - private authService = inject(AuthService); - private readonly productFacade = inject(ProductFacade); - - constructor( - private route: ActivatedRoute, - private apiService: ApiService, - private cartService: CartService, - private telegramService: TelegramService, - private sanitizer: DomSanitizer, - private languageService: LanguageService - ) {} - - ngOnInit(): void { - this.routeSubscription = this.route.params.subscribe(params => { - const id = parseInt(params['id'], 10); - this.loadItem(id); - }); - } - - ngOnDestroy(): void { - this.routeSubscription?.unsubscribe(); - if (this.reviewResetTimeout) clearTimeout(this.reviewResetTimeout); - if (this.reviewErrorTimeout) clearTimeout(this.reviewErrorTimeout); - if (this.reloadTimeout) clearTimeout(this.reloadTimeout); - this.seoService.resetToDefaults(); - } - - loadItem(itemID: number): void { - this.loading.set(true); - - this.productFacade.getProduct(itemID).subscribe({ - next: (item) => { - this.item.set(item); - this.initVariantSelection(item); - this.seoService.setItemMeta(item); - this.loadRelatedProducts(item); - this.loading.set(false); - }, - error: (err) => { - this.error.set('Failed to load item'); - this.loading.set(false); - console.error('Error loading item:', err); - } - }); - } - - private initVariantSelection(item: Item): void { - // Auto-select the first available colour and size from itemDetails - const details = item.itemDetails; - if (details?.length) { - const firstColour = details[0].colour || details[0].color || null; - this.selectedColour.set(firstColour); - const firstSize = details[0].size || null; - this.selectedSize.set(firstSize); - } else { - this.selectedColour.set(item.colour ?? null); - this.selectedSize.set(item.size ?? null); - } - } - - private loadRelatedProducts(item: Item): void { - this.productFacade.getRelatedProducts({ - productID: item.itemID, - categoryID: item.categoryID, - count: 8 - }).subscribe({ - next: (result) => this.relatedProducts.set(result.items), - error: () => this.relatedProducts.set([]) - }); - } - - selectColour(colour: string): void { - this.selectedColour.set(colour); - // If current size is not available for the new colour, reset to first available - const sizes = this.availableSizes(); - if (sizes.length && this.selectedSize() && !sizes.includes(this.selectedSize()!)) { - this.selectedSize.set(sizes[0]); - } - } - - selectSize(size: string): void { - this.selectedSize.set(size); - } - - selectPhoto(index: number): void { - this.selectedPhotoIndex.set(index); - } - - addToCart(): void { - const currentItem = this.item(); - if (currentItem) { - this.cartService.addItem(currentItem.itemID, 1, { - colour: this.selectedColour() ?? undefined, - size: this.selectedSize() ?? undefined, - price: this.effectivePrice(), - currency: this.effectiveCurrency() - }); - } - } - - addRelatedToCart(itemID: number, event: Event): void { - event.preventDefault(); - event.stopPropagation(); - this.cartService.addItem(itemID); - } - - getDiscountedPrice(): number { - const currentItem = this.item(); - if (!currentItem) return 0; - const price = this.effectivePrice(); - const discount = currentItem.discount || 0; - return discount > 0 ? price * (1 - discount / 100) : price; - } - - isDigitalDelivery(): boolean { - return this.item()?.deliveryMode === 'digital'; - } - - getDeliveryCurrency(): string { - return this.effectiveCurrency() || this.item()?.currency || 'RUB'; - } - - trackByDeliveryOption(index: number, option: DeliveryOption): string { - return `${index}-${option.deliveryPlace}-${option.deliveryTime}-${option.deliveryPrice}`; - } - - // BackOffice integration helpers - - getItemName(): string { - const currentItem = this.item(); - if (!currentItem) return ''; - const lang = this.languageService.currentLanguage(); - return getTranslatedField(currentItem, 'name', lang); - } - - relatedItemName(item: Item): string { - return getTranslatedField(item, 'name', this.languageService.currentLanguage()); - } - - getSimpleDescription(): string { - const currentItem = this.item(); - if (!currentItem) return ''; - return currentItem.simpleDescription || currentItem.description || ''; - } - - hasDescriptionFields(): boolean { - const currentItem = this.item(); - return !!(currentItem?.descriptionFields && currentItem.descriptionFields.length > 0); - } - - getTranslatedDescriptionFields(): DescriptionField[] { - const currentItem = this.item(); - if (!currentItem) return []; - const lang = this.languageService.currentLanguage(); - const translation = currentItem.translations?.[lang]; - if (translation?.description && translation.description.length > 0) { - return translation.description; - } - return currentItem.descriptionFields || []; - } - - getStockClass(): string { - const currentItem = this.item(); - if (!currentItem) return 'high'; - return getStockStatus(currentItem); - } - - getStockLabel(): string { - const status = this.getStockClass(); - switch (status) { - case 'high': return 'В наличии'; - case 'medium': return 'Заканчивается'; - case 'low': return 'Последние штуки'; - case 'out': return 'Нет в наличии'; - default: return 'В наличии'; - } - } - - readonly getBadgeClass = getBadgeClass; - - getSafeHtml(html: string): SafeHtml { - return this.sanitizer.sanitize(SecurityContext.HTML, html) || ''; - } - - getRatingStars(rating: number): string { - const fullStars = Math.floor(rating); - const hasHalfStar = rating % 1 >= 0.5; - let stars = '★'.repeat(fullStars); - if (hasHalfStar) stars += '☆'; - return stars; - } - - formatDate(timestamp: string): string { - const date = new Date(timestamp); - const now = new Date(); - const diffMs = now.getTime() - date.getTime(); - const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); - - if (diffDays === 0) return this.i18n.t('itemDetail.today'); - if (diffDays === 1) return this.i18n.t('itemDetail.yesterday'); - if (diffDays < 7) return `${diffDays} ${this.i18n.t('itemDetail.daysAgo')}`; - if (diffDays < 30) return `${Math.floor(diffDays / 7)} ${this.i18n.t('itemDetail.weeksAgo')}`; - - return date.toLocaleDateString('ru-RU', { - day: 'numeric', - month: 'long', - year: 'numeric' - }); - } - - setRating(rating: number): void { - this.newReview.rating = rating; - } - - getUserDisplayName(): string | null { - if (!this.telegramService.isTelegramApp()) { - return this.i18n.t('itemDetail.defaultUser'); - } - return this.telegramService.getDisplayName(); - } - - submitReview(): void { - if (!this.newReview.rating || !this.newReview.comment.trim()) { - return; - } - - const currentItem = this.item(); - if (!currentItem) return; - - this.reviewSubmitStatus.set('loading'); - - const reviewData = { - itemID: currentItem.itemID, - rating: this.newReview.rating, - comment: this.newReview.comment.trim(), - sessionID: this.authService.session()?.sessionId || '', - timestamp: new Date().toISOString() - }; - - this.apiService.submitReview(reviewData).subscribe({ - next: () => { - this.reviewSubmitStatus.set('success'); - this.newReview = { rating: 0, comment: '', anonymous: false }; - - // Сброс состояния через 3 секунды - this.reviewResetTimeout = setTimeout(() => { - this.reviewSubmitStatus.set('idle'); - }, 3000); - - // Перезагрузить данные товара после отправки отзыва - this.reloadTimeout = setTimeout(() => { - this.loadItem(currentItem.itemID); - }, 500); - }, - error: (err) => { - console.error('Error submitting review:', err); - this.reviewSubmitStatus.set('error'); - - // Сброс состояния об ошибке через 5 секунд - this.reviewErrorTimeout = setTimeout(() => { - this.reviewSubmitStatus.set('idle'); - }, 5000); - } - }); - } -} diff --git a/src/app/pages/legal/company-details/company-details.component.html b/src/app/pages/legal/company-details/company-details.component.html deleted file mode 100644 index da787de..0000000 --- a/src/app/pages/legal/company-details/company-details.component.html +++ /dev/null @@ -1,5 +0,0 @@ -@switch (lang()) { - @case ('ru') { } - @case ('en') { } - @case ('hy') { } -} diff --git a/src/app/pages/legal/company-details/company-details.component.scss b/src/app/pages/legal/company-details/company-details.component.scss deleted file mode 100644 index 8775e75..0000000 --- a/src/app/pages/legal/company-details/company-details.component.scss +++ /dev/null @@ -1,95 +0,0 @@ -@use '../../../../styles/shared-legal.scss'; - -// Specific styles for company-details only -.requisites { - display: flex; - flex-direction: column; - gap: 0.75rem; - - .req-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.75rem; - background: var(--bg-secondary); - border-radius: var(--radius-sm); - - .req-label { - font-weight: var(--font-weight-medium, 500); - color: var(--text-secondary); - } - - .req-value { - font-family: 'Courier New', monospace; - color: var(--text-primary); - font-weight: var(--font-weight-semibold, 600); - } - } -} - -.work-hours { - padding: 1rem; - background: var(--bg-tertiary); - border-radius: var(--radius-md); - border-left: 4px solid var(--primary-color); - margin-top: 1.5rem; - - p { - margin: 0.5rem 0; - color: var(--text-primary); - - strong { - color: var(--primary-color); - } - } -} - -.org-name { - font-weight: var(--font-weight-semibold, 600); - color: var(--text-primary); - margin-bottom: 0.75rem; -} - -.org-short { - font-size: var(--font-size-md, 0.9375rem); - /* --text-light fails WCAG AA 4.5:1 at this font size across every theme - (dexar 3.39:1, lavero/novo 2.54:1) — using --text-secondary instead, - which passes (4.55:1 / 7.56:1) with no visual-family change. */ - color: var(--text-secondary); -} - -.basis { - font-size: var(--font-size-base, 0.875rem); - color: var(--text-secondary); - margin-top: 0.5rem; -} - -.details-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 1rem; - - .detail-item { - display: flex; - flex-direction: column; - gap: 0.5rem; - - strong { - color: var(--text-primary); - font-weight: var(--font-weight-semibold, 600); - } - - span { - color: var(--text-secondary); - - a { - color: var(--primary-color); - text-decoration: none; - - &:hover { - text-decoration: underline; - } - } - } - } -} diff --git a/src/app/pages/legal/company-details/company-details.component.ts b/src/app/pages/legal/company-details/company-details.component.ts deleted file mode 100644 index ecdd2d5..0000000 --- a/src/app/pages/legal/company-details/company-details.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; -import { LanguageService } from '../../../services/language.service'; -import { CompanyDetailsRuComponent } from './ru/company-details-ru.component'; -import { CompanyDetailsEnComponent } from './en/company-details-en.component'; -import { CompanyDetailsHyComponent } from './hy/company-details-hy.component'; - -@Component({ - selector: 'app-company-details', - imports: [CompanyDetailsRuComponent, CompanyDetailsEnComponent, CompanyDetailsHyComponent], - templateUrl: './company-details.component.html', - styleUrls: ['./company-details.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class CompanyDetailsComponent { - lang = inject(LanguageService).currentLanguage; -} diff --git a/src/app/pages/legal/company-details/en/company-details-en.component.html b/src/app/pages/legal/company-details/en/company-details-en.component.html deleted file mode 100644 index 871796c..0000000 --- a/src/app/pages/legal/company-details/en/company-details-en.component.html +++ /dev/null @@ -1,106 +0,0 @@ - diff --git a/src/app/pages/legal/company-details/en/company-details-en.component.ts b/src/app/pages/legal/company-details/en/company-details-en.component.ts deleted file mode 100644 index 67d3bf3..0000000 --- a/src/app/pages/legal/company-details/en/company-details-en.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-company-details-en', - templateUrl: './company-details-en.component.html', - styleUrls: ['../company-details.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class CompanyDetailsEnComponent {} diff --git a/src/app/pages/legal/company-details/hy/company-details-hy.component.html b/src/app/pages/legal/company-details/hy/company-details-hy.component.html deleted file mode 100644 index 314a78c..0000000 --- a/src/app/pages/legal/company-details/hy/company-details-hy.component.html +++ /dev/null @@ -1,104 +0,0 @@ - diff --git a/src/app/pages/legal/company-details/hy/company-details-hy.component.ts b/src/app/pages/legal/company-details/hy/company-details-hy.component.ts deleted file mode 100644 index f894dcd..0000000 --- a/src/app/pages/legal/company-details/hy/company-details-hy.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-company-details-hy', - templateUrl: './company-details-hy.component.html', - styleUrls: ['../company-details.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class CompanyDetailsHyComponent {} diff --git a/src/app/pages/legal/company-details/ru/company-details-ru.component.html b/src/app/pages/legal/company-details/ru/company-details-ru.component.html deleted file mode 100644 index 48ad089..0000000 --- a/src/app/pages/legal/company-details/ru/company-details-ru.component.html +++ /dev/null @@ -1,106 +0,0 @@ - diff --git a/src/app/pages/legal/company-details/ru/company-details-ru.component.ts b/src/app/pages/legal/company-details/ru/company-details-ru.component.ts deleted file mode 100644 index e7c8eed..0000000 --- a/src/app/pages/legal/company-details/ru/company-details-ru.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-company-details-ru', - templateUrl: './company-details-ru.component.html', - styleUrls: ['../company-details.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class CompanyDetailsRuComponent {} diff --git a/src/app/pages/legal/payment-terms/en/payment-terms-en.component.html b/src/app/pages/legal/payment-terms/en/payment-terms-en.component.html deleted file mode 100644 index 071985a..0000000 --- a/src/app/pages/legal/payment-terms/en/payment-terms-en.component.html +++ /dev/null @@ -1,117 +0,0 @@ - diff --git a/src/app/pages/legal/payment-terms/en/payment-terms-en.component.ts b/src/app/pages/legal/payment-terms/en/payment-terms-en.component.ts deleted file mode 100644 index 3457de6..0000000 --- a/src/app/pages/legal/payment-terms/en/payment-terms-en.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { RouterLink } from '@angular/router'; -import { LangRoutePipe } from '../../../../pipes/lang-route.pipe'; - -@Component({ - selector: 'app-payment-terms-en', - imports: [RouterLink, LangRoutePipe], - templateUrl: './payment-terms-en.component.html', - styleUrls: ['../payment-terms.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PaymentTermsEnComponent {} diff --git a/src/app/pages/legal/payment-terms/hy/payment-terms-hy.component.html b/src/app/pages/legal/payment-terms/hy/payment-terms-hy.component.html deleted file mode 100644 index ae9a15b..0000000 --- a/src/app/pages/legal/payment-terms/hy/payment-terms-hy.component.html +++ /dev/null @@ -1,117 +0,0 @@ - diff --git a/src/app/pages/legal/payment-terms/hy/payment-terms-hy.component.ts b/src/app/pages/legal/payment-terms/hy/payment-terms-hy.component.ts deleted file mode 100644 index 9cccefc..0000000 --- a/src/app/pages/legal/payment-terms/hy/payment-terms-hy.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { RouterLink } from '@angular/router'; -import { LangRoutePipe } from '../../../../pipes/lang-route.pipe'; - -@Component({ - selector: 'app-payment-terms-hy', - imports: [RouterLink, LangRoutePipe], - templateUrl: './payment-terms-hy.component.html', - styleUrls: ['../payment-terms.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PaymentTermsHyComponent {} diff --git a/src/app/pages/legal/payment-terms/payment-terms.component.html b/src/app/pages/legal/payment-terms/payment-terms.component.html deleted file mode 100644 index 3761048..0000000 --- a/src/app/pages/legal/payment-terms/payment-terms.component.html +++ /dev/null @@ -1,6 +0,0 @@ -@switch (lang()) { - @case ('ru') { } - @case ('en') { } - @case ('hy') { } -} - diff --git a/src/app/pages/legal/payment-terms/payment-terms.component.scss b/src/app/pages/legal/payment-terms/payment-terms.component.scss deleted file mode 100644 index c410e98..0000000 --- a/src/app/pages/legal/payment-terms/payment-terms.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@use '../../../../styles/shared-legal.scss'; - diff --git a/src/app/pages/legal/payment-terms/payment-terms.component.ts b/src/app/pages/legal/payment-terms/payment-terms.component.ts deleted file mode 100644 index e7c7da5..0000000 --- a/src/app/pages/legal/payment-terms/payment-terms.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; -import { LanguageService } from '../../../services/language.service'; -import { PaymentTermsRuComponent } from './ru/payment-terms-ru.component'; -import { PaymentTermsEnComponent } from './en/payment-terms-en.component'; -import { PaymentTermsHyComponent } from './hy/payment-terms-hy.component'; - -@Component({ - selector: 'app-payment-terms', - imports: [PaymentTermsRuComponent, PaymentTermsEnComponent, PaymentTermsHyComponent], - templateUrl: './payment-terms.component.html', - styleUrls: ['./payment-terms.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PaymentTermsComponent { - lang = inject(LanguageService).currentLanguage; -} diff --git a/src/app/pages/legal/payment-terms/ru/payment-terms-ru.component.html b/src/app/pages/legal/payment-terms/ru/payment-terms-ru.component.html deleted file mode 100644 index 4c47239..0000000 --- a/src/app/pages/legal/payment-terms/ru/payment-terms-ru.component.html +++ /dev/null @@ -1,117 +0,0 @@ - diff --git a/src/app/pages/legal/payment-terms/ru/payment-terms-ru.component.ts b/src/app/pages/legal/payment-terms/ru/payment-terms-ru.component.ts deleted file mode 100644 index 4a2d31c..0000000 --- a/src/app/pages/legal/payment-terms/ru/payment-terms-ru.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { RouterLink } from '@angular/router'; -import { LangRoutePipe } from '../../../../pipes/lang-route.pipe'; - -@Component({ - selector: 'app-payment-terms-ru', - imports: [RouterLink, LangRoutePipe], - templateUrl: './payment-terms-ru.component.html', - styleUrls: ['../payment-terms.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PaymentTermsRuComponent {} diff --git a/src/app/pages/legal/privacy-policy/en/privacy-policy-en.component.html b/src/app/pages/legal/privacy-policy/en/privacy-policy-en.component.html deleted file mode 100644 index 16495db..0000000 --- a/src/app/pages/legal/privacy-policy/en/privacy-policy-en.component.html +++ /dev/null @@ -1,367 +0,0 @@ - diff --git a/src/app/pages/legal/privacy-policy/en/privacy-policy-en.component.ts b/src/app/pages/legal/privacy-policy/en/privacy-policy-en.component.ts deleted file mode 100644 index 2e5766f..0000000 --- a/src/app/pages/legal/privacy-policy/en/privacy-policy-en.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-privacy-policy-en', - templateUrl: './privacy-policy-en.component.html', - styleUrls: ['../privacy-policy.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PrivacyPolicyEnComponent {} diff --git a/src/app/pages/legal/privacy-policy/hy/privacy-policy-hy.component.html b/src/app/pages/legal/privacy-policy/hy/privacy-policy-hy.component.html deleted file mode 100644 index f6d7d04..0000000 --- a/src/app/pages/legal/privacy-policy/hy/privacy-policy-hy.component.html +++ /dev/null @@ -1,367 +0,0 @@ - \ No newline at end of file diff --git a/src/app/pages/legal/privacy-policy/hy/privacy-policy-hy.component.ts b/src/app/pages/legal/privacy-policy/hy/privacy-policy-hy.component.ts deleted file mode 100644 index e6bc01d..0000000 --- a/src/app/pages/legal/privacy-policy/hy/privacy-policy-hy.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-privacy-policy-hy', - templateUrl: './privacy-policy-hy.component.html', - styleUrls: ['../privacy-policy.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PrivacyPolicyHyComponent {} diff --git a/src/app/pages/legal/privacy-policy/privacy-policy.component.html b/src/app/pages/legal/privacy-policy/privacy-policy.component.html deleted file mode 100644 index a419fa0..0000000 --- a/src/app/pages/legal/privacy-policy/privacy-policy.component.html +++ /dev/null @@ -1,5 +0,0 @@ -@switch (lang()) { - @case ('ru') { } - @case ('en') { } - @case ('hy') { } -} \ No newline at end of file diff --git a/src/app/pages/legal/privacy-policy/privacy-policy.component.scss b/src/app/pages/legal/privacy-policy/privacy-policy.component.scss deleted file mode 100644 index 5169646..0000000 --- a/src/app/pages/legal/privacy-policy/privacy-policy.component.scss +++ /dev/null @@ -1 +0,0 @@ -@use '../../../../styles/shared-legal.scss'; diff --git a/src/app/pages/legal/privacy-policy/privacy-policy.component.ts b/src/app/pages/legal/privacy-policy/privacy-policy.component.ts deleted file mode 100644 index 46cec9b..0000000 --- a/src/app/pages/legal/privacy-policy/privacy-policy.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; -import { LanguageService } from '../../../services/language.service'; -import { PrivacyPolicyRuComponent } from './ru/privacy-policy-ru.component'; -import { PrivacyPolicyEnComponent } from './en/privacy-policy-en.component'; -import { PrivacyPolicyHyComponent } from './hy/privacy-policy-hy.component'; - -@Component({ - selector: 'app-privacy-policy', - imports: [PrivacyPolicyRuComponent, PrivacyPolicyEnComponent, PrivacyPolicyHyComponent], - templateUrl: './privacy-policy.component.html', - styleUrls: ['./privacy-policy.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PrivacyPolicyComponent { - lang = inject(LanguageService).currentLanguage; -} diff --git a/src/app/pages/legal/privacy-policy/ru/privacy-policy-ru.component.html b/src/app/pages/legal/privacy-policy/ru/privacy-policy-ru.component.html deleted file mode 100644 index 2889c9e..0000000 --- a/src/app/pages/legal/privacy-policy/ru/privacy-policy-ru.component.html +++ /dev/null @@ -1,367 +0,0 @@ - diff --git a/src/app/pages/legal/privacy-policy/ru/privacy-policy-ru.component.ts b/src/app/pages/legal/privacy-policy/ru/privacy-policy-ru.component.ts deleted file mode 100644 index b46f24b..0000000 --- a/src/app/pages/legal/privacy-policy/ru/privacy-policy-ru.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-privacy-policy-ru', - templateUrl: './privacy-policy-ru.component.html', - styleUrls: ['../privacy-policy.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PrivacyPolicyRuComponent {} diff --git a/src/app/pages/legal/public-offer/en/public-offer-en.component.html b/src/app/pages/legal/public-offer/en/public-offer-en.component.html deleted file mode 100644 index e0b39d2..0000000 --- a/src/app/pages/legal/public-offer/en/public-offer-en.component.html +++ /dev/null @@ -1,529 +0,0 @@ - diff --git a/src/app/pages/legal/public-offer/en/public-offer-en.component.ts b/src/app/pages/legal/public-offer/en/public-offer-en.component.ts deleted file mode 100644 index 2473fc1..0000000 --- a/src/app/pages/legal/public-offer/en/public-offer-en.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { RouterLink } from '@angular/router'; -import { LangRoutePipe } from '../../../../pipes/lang-route.pipe'; - -@Component({ - selector: 'app-public-offer-en', - imports: [RouterLink, LangRoutePipe], - templateUrl: './public-offer-en.component.html', - styleUrls: ['../public-offer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PublicOfferEnComponent {} diff --git a/src/app/pages/legal/public-offer/hy/public-offer-hy.component.html b/src/app/pages/legal/public-offer/hy/public-offer-hy.component.html deleted file mode 100644 index 01c52d1..0000000 --- a/src/app/pages/legal/public-offer/hy/public-offer-hy.component.html +++ /dev/null @@ -1,529 +0,0 @@ - diff --git a/src/app/pages/legal/public-offer/hy/public-offer-hy.component.ts b/src/app/pages/legal/public-offer/hy/public-offer-hy.component.ts deleted file mode 100644 index 78d8454..0000000 --- a/src/app/pages/legal/public-offer/hy/public-offer-hy.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { RouterLink } from "@angular/router"; -import { LangRoutePipe } from '../../../../pipes/lang-route.pipe'; - -@Component({ - selector: 'app-public-offer-hy', - templateUrl: './public-offer-hy.component.html', - styleUrls: ['../public-offer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [RouterLink, LangRoutePipe] -}) -export class PublicOfferHyComponent {} diff --git a/src/app/pages/legal/public-offer/public-offer.component.html b/src/app/pages/legal/public-offer/public-offer.component.html deleted file mode 100644 index 45b4990..0000000 --- a/src/app/pages/legal/public-offer/public-offer.component.html +++ /dev/null @@ -1,5 +0,0 @@ -@switch (lang()) { - @case ('ru') { } - @case ('en') { } - @case ('hy') { } -} \ No newline at end of file diff --git a/src/app/pages/legal/public-offer/public-offer.component.scss b/src/app/pages/legal/public-offer/public-offer.component.scss deleted file mode 100644 index c410e98..0000000 --- a/src/app/pages/legal/public-offer/public-offer.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@use '../../../../styles/shared-legal.scss'; - diff --git a/src/app/pages/legal/public-offer/public-offer.component.ts b/src/app/pages/legal/public-offer/public-offer.component.ts deleted file mode 100644 index 6a42910..0000000 --- a/src/app/pages/legal/public-offer/public-offer.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; -import { LanguageService } from '../../../services/language.service'; -import { PublicOfferEnComponent } from './en/public-offer-en.component'; -import { PublicOfferHyComponent } from './hy/public-offer-hy.component'; -import { PublicOfferRuComponent } from './ru/public-offer-ru.component'; - -@Component({ - selector: 'app-public-offer', - imports: [PublicOfferRuComponent, PublicOfferEnComponent, PublicOfferHyComponent], - templateUrl: './public-offer.component.html', - styleUrls: ['./public-offer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PublicOfferComponent { - lang = inject(LanguageService).currentLanguage; -} diff --git a/src/app/pages/legal/public-offer/ru/public-offer-ru.component.html b/src/app/pages/legal/public-offer/ru/public-offer-ru.component.html deleted file mode 100644 index e30b522..0000000 --- a/src/app/pages/legal/public-offer/ru/public-offer-ru.component.html +++ /dev/null @@ -1,529 +0,0 @@ - diff --git a/src/app/pages/legal/public-offer/ru/public-offer-ru.component.ts b/src/app/pages/legal/public-offer/ru/public-offer-ru.component.ts deleted file mode 100644 index 8ed99c1..0000000 --- a/src/app/pages/legal/public-offer/ru/public-offer-ru.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { RouterLink } from '@angular/router'; -import { LangRoutePipe } from '../../../../pipes/lang-route.pipe'; - -@Component({ - selector: 'app-public-offer-ru', - imports: [RouterLink, LangRoutePipe], - templateUrl: './public-offer-ru.component.html', - styleUrls: ['../public-offer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PublicOfferRuComponent {} diff --git a/src/app/pages/legal/return-policy/en/return-policy-en.component.html b/src/app/pages/legal/return-policy/en/return-policy-en.component.html deleted file mode 100644 index 29cf24e..0000000 --- a/src/app/pages/legal/return-policy/en/return-policy-en.component.html +++ /dev/null @@ -1,134 +0,0 @@ - diff --git a/src/app/pages/legal/return-policy/en/return-policy-en.component.ts b/src/app/pages/legal/return-policy/en/return-policy-en.component.ts deleted file mode 100644 index ae630be..0000000 --- a/src/app/pages/legal/return-policy/en/return-policy-en.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-return-policy-en', - templateUrl: './return-policy-en.component.html', - styleUrls: ['../return-policy.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ReturnPolicyEnComponent {} diff --git a/src/app/pages/legal/return-policy/hy/return-policy-hy.component.html b/src/app/pages/legal/return-policy/hy/return-policy-hy.component.html deleted file mode 100644 index 6cdc282..0000000 --- a/src/app/pages/legal/return-policy/hy/return-policy-hy.component.html +++ /dev/null @@ -1,134 +0,0 @@ - diff --git a/src/app/pages/legal/return-policy/hy/return-policy-hy.component.ts b/src/app/pages/legal/return-policy/hy/return-policy-hy.component.ts deleted file mode 100644 index 8ae6e14..0000000 --- a/src/app/pages/legal/return-policy/hy/return-policy-hy.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-return-policy-hy', - templateUrl: './return-policy-hy.component.html', - styleUrls: ['../return-policy.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ReturnPolicyHyComponent {} diff --git a/src/app/pages/legal/return-policy/return-policy.component.html b/src/app/pages/legal/return-policy/return-policy.component.html deleted file mode 100644 index c42d8e5..0000000 --- a/src/app/pages/legal/return-policy/return-policy.component.html +++ /dev/null @@ -1,5 +0,0 @@ -@switch (lang()) { - @case ('ru') { } - @case ('en') { } - @case ('hy') { } -} diff --git a/src/app/pages/legal/return-policy/return-policy.component.scss b/src/app/pages/legal/return-policy/return-policy.component.scss deleted file mode 100644 index c410e98..0000000 --- a/src/app/pages/legal/return-policy/return-policy.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@use '../../../../styles/shared-legal.scss'; - diff --git a/src/app/pages/legal/return-policy/return-policy.component.ts b/src/app/pages/legal/return-policy/return-policy.component.ts deleted file mode 100644 index 1d3b95a..0000000 --- a/src/app/pages/legal/return-policy/return-policy.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; -import { LanguageService } from '../../../services/language.service'; -import { ReturnPolicyRuComponent } from './ru/return-policy-ru.component'; -import { ReturnPolicyEnComponent } from './en/return-policy-en.component'; -import { ReturnPolicyHyComponent } from './hy/return-policy-hy.component'; - -@Component({ - selector: 'app-return-policy', - imports: [ReturnPolicyRuComponent, ReturnPolicyEnComponent, ReturnPolicyHyComponent], - templateUrl: './return-policy.component.html', - styleUrls: ['./return-policy.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ReturnPolicyComponent { - lang = inject(LanguageService).currentLanguage; -} diff --git a/src/app/pages/legal/return-policy/ru/return-policy-ru.component.html b/src/app/pages/legal/return-policy/ru/return-policy-ru.component.html deleted file mode 100644 index ef3116c..0000000 --- a/src/app/pages/legal/return-policy/ru/return-policy-ru.component.html +++ /dev/null @@ -1,134 +0,0 @@ - diff --git a/src/app/pages/legal/return-policy/ru/return-policy-ru.component.ts b/src/app/pages/legal/return-policy/ru/return-policy-ru.component.ts deleted file mode 100644 index d942e59..0000000 --- a/src/app/pages/legal/return-policy/ru/return-policy-ru.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'app-return-policy-ru', - templateUrl: './return-policy-ru.component.html', - styleUrls: ['../return-policy.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ReturnPolicyRuComponent {} diff --git a/src/app/pages/search/search.component.html b/src/app/pages/search/search.component.html deleted file mode 100644 index 43cc007..0000000 --- a/src/app/pages/search/search.component.html +++ /dev/null @@ -1,105 +0,0 @@ -
-
-

{{ 'search.title' | translate }}

- -
- - @if (searchQuery && items().length > 0) { -
- {{ 'search.resultsCount' | translate }} {{ items().length }}@if (totalResults() > items().length) { {{ 'search.of' | translate }} {{ totalResults() }} } -
- } - - @if (loading() && items().length === 0) { -
-
-

{{ 'search.searching' | translate }}

-
- } - - @if (error()) { - - } - - @if (isQueryTooShort()) { -
-
- -
-

{{ 'search.minLength' | translate:{ count: minSearchLength } }}

-
- } - - @if (!loading() && searchQuery && !isQueryTooShort() && items().length === 0 && !error()) { -
-
- -
-

{{ 'search.noResults' | translate }}

-

{{ 'search.noResultsFor' | translate:{ query: searchQuery } }}

-

{{ 'search.noResultsHint' | translate }}

-
- } - - @if (items().length > 0) { -
- @for (item of items(); track trackByItemId($index, item)) { - - } -
- - @if (loading() && items().length > 0) { - @for (i of skeletonSlots; track i) { - - } - } - - @if (!hasMore() && items().length > 0) { -
-

{{ 'search.allLoaded' | translate }}

-
- } - } - - @if (!searchQuery) { -
-
- -
-

{{ 'search.emptyState' | translate }}

-
- } -
diff --git a/src/app/pages/search/search.component.scss b/src/app/pages/search/search.component.scss deleted file mode 100644 index 1a7cb8c..0000000 --- a/src/app/pages/search/search.component.scss +++ /dev/null @@ -1,394 +0,0 @@ -.search-container { - max-width: 1200px; - margin: 0 auto; - padding: 20px; - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; -} - -.search-header { - margin-bottom: 40px; - - h1 { - font-size: var(--font-size-4xl, 2rem); - margin-bottom: 20px; - color: #1e3c38; - text-align: center; - } -} - -.search-box { - position: relative; - max-width: 600px; - margin: 0 auto; - - .search-input { - width: 100%; - padding: 16px 50px 16px 20px; - font-size: var(--font-size-xl, 1.125rem); - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - border: 2px solid #d3dad9; - border-radius: var(--radius-lg, 13px); - outline: none; - transition: border-color 0.2s; - - &:focus { - border-color: var(--primary-color); - box-shadow: 0 0 0 3px rgba(73, 118, 113, 0.1); - } - } - - .search-icon { - position: absolute; - right: 20px; - top: 50%; - transform: translateY(-50%); - display: flex; - align-items: center; - color: #697777; - pointer-events: none; - } -} - -.results-count { - text-align: center; - margin-bottom: 24px; - color: #1e3c38; - font-size: var(--font-size-xl, 1.125rem); -} - -.empty-state, -.loading-initial { - text-align: center; - padding: 60px 20px; - color: #697777; -} - -.empty-state { - display: flex; - flex-direction: column; - align-items: center; - padding: 80px 20px; - - .empty-state-icon { - width: 90px; - height: 90px; - display: flex; - align-items: center; - justify-content: center; - background: #f5f3f9; - border-radius: 50%; - margin-bottom: 20px; - color: #d3dad9; - } - - p { - font-size: var(--font-size-lg, 1rem); - color: #a1b4b5; - margin: 0; - } -} - -.no-results { - text-align: center; - padding: 80px 20px; - display: flex; - flex-direction: column; - align-items: center; - - .no-results-icon { - width: 90px; - height: 90px; - display: flex; - align-items: center; - justify-content: center; - background: #f5f3f9; - border-radius: 50%; - margin-bottom: 20px; - color: #a1b4b5; - } - - h2 { - font-size: var(--font-size-2xl, 1.25rem); - font-weight: var(--font-weight-bold, 700); - color: #1e3c38; - margin: 0 0 8px 0; - } - - p { - margin: 4px 0; - color: #697777; - font-size: var(--font-size-md, 0.9375rem); - } - - .hint { - font-size: var(--font-size-base, 0.875rem); - color: #a1b4b5; - margin-top: 12px; - } -} - -.error { - text-align: center; - padding: 60px 20px; - - button { - margin-top: 20px; - padding: 10px 24px; - background: var(--primary-color); - color: white; - border: none; - border-radius: var(--radius-lg, 13px); - cursor: pointer; - font-size: var(--font-size-lg, 1rem); - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - - &:hover { - background: var(--primary-hover); - } - - &:focus-visible { - outline: 2px solid var(--primary-color); - outline-offset: 2px; - } - } -} - -.items-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - gap: 24px; - margin-bottom: 40px; -} - -.item-card { - background: white; - border-radius: var(--radius-lg, 13px); - border: 1px solid #d3dad9; - box-shadow: 0 3px 4px rgba(0, 0, 0, 0.08); - overflow: hidden; - transition: transform 0.2s, box-shadow 0.2s; - display: flex; - flex-direction: column; - - &:hover { - transform: translateY(-4px); - box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12); - } -} - -.item-link { - text-decoration: none; - color: inherit; - flex: 1; - display: flex; - flex-direction: column; -} - -.item-image { - position: relative; - width: 100%; - padding-top: 75%; - background: #f5f5f5; - overflow: hidden; - border-radius: var(--radius-lg, 13px); - - img { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: contain; - background: white; - padding: 12px; - transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), filter 0.4s ease; - filter: drop-shadow(0 2px 6px rgba(0,0,0,0.06)); - } - - &:hover img { - transform: scale(1.06) translateY(-2px); - filter: drop-shadow(0 12px 24px rgba(0,0,0,0.15)) brightness(1.03); - } -} - -.discount-badge { - position: absolute; - top: 12px; - right: 12px; - background: #ef4444; - color: white; - padding: 6px 12px; - border-radius: 20px; - font-weight: var(--font-weight-semibold, 600); - font-size: var(--font-size-base, 0.875rem); -} - -.item-details { - padding: 16px; - flex: 1; - display: flex; - flex-direction: column; - gap: 8px; -} - -.item-name { - font-size: var(--font-size-xl, 1.125rem); - margin: 0; - color: #1e3c38; - display: -webkit-box; - -webkit-line-clamp: 2; - line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; -} - -.item-rating { - display: flex; - align-items: center; - gap: 8px; - font-size: var(--font-size-base, 0.875rem); - color: #1e3c38; - - .rating-stars { - color: #497671; - font-weight: var(--font-weight-semibold, 600); - } -} - -.item-price { - display: flex; - align-items: center; - gap: 8px; - flex-wrap: wrap; - margin-top: auto; -} - -.original-price { - text-decoration: line-through; - color: #697777; - font-size: var(--font-size-base, 0.875rem); -} - -.discounted-price, -.current-price { - font-size: var(--font-size-3xl, 1.5rem); - font-weight: var(--font-weight-bold, 700); - color: var(--primary-color); -} - -.item-stock { - .stock-bar { - display: flex; - gap: 4px; - align-items: center; - - .bar-segment { - width: 20px; - height: 6px; - background: #d3dad9; - border-radius: 3px; - transition: background 0.2s; - - &.filled.high { - background: #2ed573; - } - - &.filled.medium { - background: #ffa502; - } - - &.filled.low { - background: #ef4444; - } - } - } -} - -.add-to-cart-btn { - width: 100%; - padding: 12px; - background: var(--primary-color); - color: white; - border: none; - border-radius: 0 0 13px 13px; - font-size: var(--font-size-lg, 1rem); - font-weight: var(--font-weight-semibold, 600); - font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - cursor: pointer; - transition: background 0.2s; - - &:hover { - background: var(--primary-hover); - } - - &:active { - transform: scale(0.98); - } -} - -.loading-more { - text-align: center; - padding: 40px 20px; -} - -.spinner { - width: 40px; - height: 40px; - border: 4px solid #f3f3f3; - border-top: 4px solid var(--primary-color); - border-radius: 50%; - animation: spin 1s linear infinite; - margin: 0 auto 12px; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -.no-more { - color: #697777; - padding: 40px 20px; - text-align: center; -} - -// Skeleton loading cards (shimmer/animation owned by shared app-skeleton) -.skeleton-card { - pointer-events: none; - - .item-image { - display: block; - aspect-ratio: 1 / 1; - } - - .item-details app-skeleton { - display: block; - margin-bottom: var(--space-sm, 8px); - } - - .skeleton-btn { - display: block; - border-radius: 0 0 13px 13px; - margin-top: -1px; - } -} - -@media (max-width: 768px) { - .search-header h1 { - font-size: var(--font-size-3xl, 1.5rem); - } - - .items-grid { - grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); - gap: 16px; - } - - .item-name { - font-size: var(--font-size-md, 0.9375rem); - } - - .discounted-price, - .current-price { - font-size: var(--font-size-xl, 1.125rem); - } -} diff --git a/src/app/pages/search/search.component.ts b/src/app/pages/search/search.component.ts deleted file mode 100644 index 36dd8bf..0000000 --- a/src/app/pages/search/search.component.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { Component, signal, HostListener, OnDestroy, ChangeDetectionStrategy, inject } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { CartService } from '../../services'; -import { PrefetchService } from '../../services/prefetch.service'; -import { Item } from '../../models'; -import { Subject, Subscription } from 'rxjs'; -import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; -import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField } from '../../utils/item.utils'; -import { LanguageService } from '../../services/language.service'; -import { TranslatePipe } from '../../i18n/translate.pipe'; -import { TranslateService } from '../../i18n/translate.service'; -import { SEARCH_DEBOUNCE_MS, ITEMS_PER_PAGE, SCROLL_THRESHOLD_PX, SCROLL_DEBOUNCE_MS } from '../../config/constants'; -import { ProductFacade } from '../../facades/platform/product.facade'; -import { ProductCardComponent } from '../../components/product-card/product-card.component'; -import { IconComponent } from '../../shared/ui/icon/icon.component'; -import { SkeletonComponent } from '../../shared/ui/skeleton/skeleton.component'; - -@Component({ - selector: 'app-search', - imports: [FormsModule, TranslatePipe, ProductCardComponent, IconComponent, SkeletonComponent], - templateUrl: './search.component.html', - styleUrls: ['./search.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class SearchComponent implements OnDestroy { - readonly minSearchLength = 3; - searchQuery = ''; - items = signal([]); - loading = signal(false); - error = signal(null); - hasMore = signal(true); - totalResults = signal(0); - - private skip = 0; - private readonly count = ITEMS_PER_PAGE; - private isLoadingMore = false; - private searchSubject = new Subject(); - private searchSubscription: Subscription; - private i18n = inject(TranslateService); - private readonly productFacade = inject(ProductFacade); - - constructor( - private cartService: CartService, - private prefetchService: PrefetchService - ) { - this.searchSubscription = this.searchSubject - .pipe( - debounceTime(SEARCH_DEBOUNCE_MS), - distinctUntilChanged() - ) - .subscribe(query => { - if (query.trim().length >= 3 || query.trim().length === 0) { - this.performSearch(query); - } - }); - } - - ngOnDestroy(): void { - this.searchSubscription.unsubscribe(); - this.searchSubject.complete(); - if (this.scrollTimeout) clearTimeout(this.scrollTimeout); - } - - onSearchInput(query: string): void { - this.searchQuery = query; - this.error.set(null); - - if (this.isQueryTooShort()) { - this.resetSearchState(); - return; - } - - this.searchSubject.next(query); - } - - performSearch(query: string): void { - if (!query.trim()) { - this.resetSearchState(); - return; - } - - this.error.set(null); - this.items.set([]); - this.skip = 0; - this.hasMore.set(true); - this.totalResults.set(0); - this.loadResults(); - } - - loadResults(): void { - if (this.isLoadingMore || !this.hasMore() || !this.searchQuery.trim()) return; - - this.loading.set(true); - this.isLoadingMore = true; - - this.productFacade.searchProducts({ search: this.searchQuery.trim(), count: this.count, skip: this.skip }).subscribe({ - next: (response) => { - // Update total results (only on first load) - if (this.skip === 0) { - this.totalResults.set(response.total); - } - - // Handle empty results - if (!response.items || response.items.length === 0) { - this.hasMore.set(false); - } else { - // Check if there are more items to load - if (response.items.length < this.count || this.skip + response.items.length >= response.total) { - this.hasMore.set(false); - } - this.items.update(current => [...current, ...response.items]); - this.skip += response.items.length; - } - this.loading.set(false); - this.isLoadingMore = false; - }, - error: (err) => { - this.error.set(this.i18n.t('home.errorTitle')); - this.loading.set(false); - this.isLoadingMore = false; - console.error('Error searching items:', err); - } - }); - } - - private resetSearchState(): void { - this.items.set([]); - this.loading.set(false); - this.hasMore.set(false); - this.totalResults.set(0); - this.skip = 0; - this.isLoadingMore = false; - } - - isQueryTooShort(): boolean { - const length = this.searchQuery.trim().length; - return length > 0 && length < this.minSearchLength; - } - - private scrollTimeout?: ReturnType; - - @HostListener('window:scroll') - onScroll(): void { - if (this.scrollTimeout) clearTimeout(this.scrollTimeout); - - this.scrollTimeout = setTimeout(() => { - const scrollPosition = window.innerHeight + window.scrollY; - const bottomPosition = document.documentElement.scrollHeight - SCROLL_THRESHOLD_PX; - - if (scrollPosition >= bottomPosition && !this.loading() && this.hasMore()) { - this.loadResults(); - } - }, SCROLL_DEBOUNCE_MS); - } - - addToCart(itemID: number, event: Event): void { - event.preventDefault(); - event.stopPropagation(); - this.cartService.addItem(itemID); - } - - onItemHover(itemID: number): void { - this.prefetchService.prefetchItem(itemID); - } - - readonly skeletonSlots = Array.from({ length: 8 }); - readonly getDiscountedPrice = getDiscountedPrice; - readonly getMainImage = getMainImage; - readonly trackByItemId = trackByItemId; - readonly getBadgeClass = getBadgeClass; - - private langService = inject(LanguageService); - itemName(item: Item): string { return getTranslatedField(item, 'name', this.langService.currentLanguage()); } - itemDesc(item: Item): string { return getTranslatedField(item, 'simpleDescription', this.langService.currentLanguage()); } -}