From 939641c5616f7ae71d6f9c5c69b7f995222da145 Mon Sep 17 00:00:00 2001 From: Hakwoo Kim Date: Wed, 3 Aug 2022 10:13:43 -0400 Subject: [PATCH 001/175] asm360 overview & profile --- .../asm/assets/translations/en/asm.ts | 22 ++ .../asm/components/asm-components.module.ts | 16 + .../asm-customer-360.component.html | 59 ++++ .../asm-customer-overview.component.html | 176 ++++++++++ .../asm-customer-overview.component.spec.ts | 0 .../asm-customer-overview.component.ts | 326 ++++++++++++++++++ .../asm-customer-overview.model.ts | 5 + .../asm-product-item.component.html | 26 ++ .../asm-product-item.component.spec.ts | 0 .../asm-product-item.component.ts | 19 + .../asm-customer-profile.component.html | 88 +++++ .../asm-customer-profile.component.spec.ts | 0 .../asm-customer-profile.component.ts | 286 +++++++++++++++ .../asm-customer-profile.model.ts | 9 + .../components/_asm-customer-overview.scss | 90 +++++ .../components/_asm-customer-profile.scss | 47 +++ .../asm/styles/components/_index.scss | 7 +- .../storefrontstyles/scss/_versioning.scss | 2 +- 18 files changed, 1176 insertions(+), 2 deletions(-) create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.html create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.spec.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.model.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.html create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.spec.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.html create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.spec.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.model.ts create mode 100644 feature-libs/asm/styles/components/_asm-customer-overview.scss create mode 100644 feature-libs/asm/styles/components/_asm-customer-profile.scss diff --git a/feature-libs/asm/assets/translations/en/asm.ts b/feature-libs/asm/assets/translations/en/asm.ts index e33a5e7a33e..c9f35149f89 100644 --- a/feature-libs/asm/assets/translations/en/asm.ts +++ b/feature-libs/asm/assets/translations/en/asm.ts @@ -70,6 +70,28 @@ export const asm = { enterCartId: 'Enter cart id', resetCartId: 'Reset', }, + profileAddresses: { + billingAddress: 'Billing Address', + deliveryAddress: 'Delivery Address', + phone1: 'Phone1', + phone2: 'Phone2', + paymentMethodHeader: 'Saved Payment Methods' + }, + customerOverview: { + activeCart: 'Active Cart', + activeCartCode: 'Active Cart {{code}}', + totalNoItems: 'Total No. Items {{count}}', + totalPrice: 'Total Price {{price}}', + noActiveCart: 'There are currently no Active Cart Items', + savedCartCode: 'Last Saved Cart {{code}}', + savedCart: 'Last Saved Cart', + noSavedCart: 'There are currently no Saved Cart items', + interests: 'Interests', + noInterests: 'There are currently no Interest items', + inStock: 'In Stock', + lowStock: 'Low Stock', + outOfStock: 'Out of Stock', + }, csagentTokenExpired: 'Your customer support agent session is expired.', endSession: 'End Session', agentSessionTimer: { diff --git a/feature-libs/asm/components/asm-components.module.ts b/feature-libs/asm/components/asm-components.module.ts index 5ee8e4ceb7e..6b87d347b47 100644 --- a/feature-libs/asm/components/asm-components.module.ts +++ b/feature-libs/asm/components/asm-components.module.ts @@ -7,22 +7,30 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; import { NgSelectModule } from '@ng-select/ng-select'; import { FeaturesConfigModule, I18nModule, provideConfig, + UrlModule, } from '@spartacus/core'; import { + CardModule, FormErrorsModule, IconModule, KeyboardFocusModule, NgSelectA11yModule, + MediaModule, PasswordVisibilityToggleModule, SortingModule, SpinnerModule, } from '@spartacus/storefront'; import { AsmBindCartComponent } from './asm-bind-cart/asm-bind-cart.component'; +import { AsmCustomer360Component } from './asm-customer-360/asm-customer-360.component'; +import { AsmCustomerOverviewComponent } from './asm-customer-360/asm-customer-overview/asm-customer-overview.component'; +import { AsmProductItemComponent } from './asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component'; +import { AsmCustomerProfileComponent } from './asm-customer-360/asm-customer-profile/asm-customer-profile.component'; import { AsmMainUiComponent } from './asm-main-ui/asm-main-ui.component'; import { AsmSessionTimerComponent } from './asm-session-timer/asm-session-timer.component'; import { FormatTimerPipe } from './asm-session-timer/format-timer.pipe'; @@ -50,6 +58,10 @@ import { DotSpinnerComponent } from './dot-spinner/dot-spinner.component'; NgSelectA11yModule, SortingModule, FeaturesConfigModule, + CardModule, + MediaModule, + RouterModule, + UrlModule, ], declarations: [ AsmMainUiComponent, @@ -60,8 +72,12 @@ import { DotSpinnerComponent } from './dot-spinner/dot-spinner.component'; FormatTimerPipe, CustomerEmulationComponent, AsmToggleUiComponent, + AsmCustomer360Component, AsmBindCartComponent, DotSpinnerComponent, + AsmCustomerProfileComponent, + AsmCustomerOverviewComponent, + AsmProductItemComponent ], exports: [ AsmMainUiComponent, diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html new file mode 100644 index 00000000000..bb151609858 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html @@ -0,0 +1,59 @@ +
+ + + + + + + +
+ + + + diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.html new file mode 100644 index 00000000000..d5827e86e6d --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.html @@ -0,0 +1,176 @@ + +
+
+
+

+ {{ 'asm.customerOverview.activeCart' | cxTranslate }} + + {{ activeCart.code }} + +

+
+
+ {{ + 'asm.customerOverview.totalNoItems' + | cxTranslate: { count: activeCart.totalItems } + }} +
+
+ {{ + 'asm.customerOverview.totalPrice' + | cxTranslate: { price: activeCart.totalPrice.formattedValue } + }} +
+
+
+
+ +
+
+
+ +
+
+
+

+ {{ 'asm.customerOverview.savedCart' | cxTranslate }} + + {{ savedCart.code }} + +

+
+
+ {{ + 'asm.customerOverview.totalNoItems' + | cxTranslate: { count: savedCart.totalItems } + }} +
+
+ {{ + 'asm.customerOverview.totalPrice' + | cxTranslate: { price: savedCart.totalPrice.formattedValue } + }} +
+
+
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+ + + + + + + + + + + + + + +
+
+ {{ title }} +
+
+ {{ description }} +
+
+
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.spec.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.spec.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.ts new file mode 100644 index 00000000000..057100688bf --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.ts @@ -0,0 +1,326 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { AsmConfig } from '@spartacus/asm/core'; +import { Cart } from '@spartacus/cart/base/root'; + +import { + ImageType, + PriceType, + Product, + RoutingService, + User, +} from '@spartacus/core'; + +import { Observable, of, Subscription } from 'rxjs'; +import { AsmInterestEntry } from './asm-customer-overview.model'; + +@Component({ + selector: 'cx-asm-customer-overview', + templateUrl: './asm-customer-overview.component.html', +}) +export class AsmCustomerOverviewComponent implements OnInit { + @Input() customer: User; + + activeCart$: Observable; + + savedCart$: Observable; + + interests$: Observable; + + protected subscription = new Subscription(); + + constructor( + protected asmConfig: AsmConfig, + protected routingService: RoutingService + ) {} + + ngOnInit(): void { + if (this.customer?.uid) { + this.activeCart$ = of(this.getMockCartData()); + this.savedCart$ = of(this.getMockCartData()); + this.interests$ = of(this.getMockInterestData()); + } + } + + onSelectProduct(selectedProduct: Product): void { + this.routingService.go({ cxRoute: 'product', params: selectedProduct }); + // TODO: we need to close main dialog + } + + onSelectCart(): void { + // TODO emulate selected user and navigate to cart... + } + goToMyInterests(): void { + // TODO emulate selected user and navigate to Interests... + this.routingService.go({ cxRoute: 'myInterests' }); + + } + + private getMockInterestData(): AsmInterestEntry { + const product: Product = { + availableForPickup: true, + baseOptions: [], + categories: [ + { + code: '827', + name: 'Power Adapters & Inverters', + }, + { + code: 'brand_10', + name: 'Canon', + }, + ], + code: '514518', + configurable: false, + images: { + PRIMARY: { + zoom: { + altText: 'ACK-E2', + format: 'zoom', + imageType: ImageType.PRIMARY, + url: '/medias/?context=bWFzdGVyfGltYWdlc3wxNTYzNHxpbWFnZS9qcGVnfGltYWdlcy9oMjQvaGNkLzg3OTcyMTk3ODI2ODYuanBnfDRjMDljZjZkMjlhYTM5ODE3Y2IwYTBmN2VlNjdiMGY1YjljYmVlYjA1YzI5MDNhNTRjODU5MTkyNDVhMzVjODg', + }, + product: { + altText: 'ACK-E2', + format: 'product', + imageType: ImageType.PRIMARY, + url: '/medias/?context=bWFzdGVyfGltYWdlc3w4MDk0fGltYWdlL2pwZWd8aW1hZ2VzL2hhOC9oMjEvODc5NzI0NjA2MjYyMi5qcGd8MDFkMTI2YzBkMTk4N2I5ZjkwNjkzY2U3NGM5YjE4ZDg1ODhiNmMyMDIyYzViYzhiNTVmYjYzYzhhYzZkZDlkNw', + }, + thumbnail: { + altText: 'ACK-E2', + format: 'thumbnail', + imageType: ImageType.PRIMARY, + url: '/medias/?context=bWFzdGVyfGltYWdlc3wyMjk5fGltYWdlL2pwZWd8aW1hZ2VzL2hhMi9oZDMvODc5NzI3MjQwODA5NC5qcGd8OWMyMDViNzE4YmIwODU1MDZiMjc5NWI1NzdlNWMyZThiZDUxY2ZkNTRiYTM5MjY2NmI4ZGY3NTJiZDFjMTRjMw', + }, + cartIcon: { + altText: 'ACK-E2', + format: 'cartIcon', + imageType: ImageType.PRIMARY, + url: '/medias/?context=bWFzdGVyfGltYWdlc3wxNTI5fGltYWdlL2pwZWd8aW1hZ2VzL2g0Yi9oOTYvODc5NzI5ODc1MzU2Ni5qcGd8OWEwMjgwOTA4ZTg0MGI5NjgzYzU3MDUyY2MwMmIwMzY2NzJjMmJmM2M2M2JlNzJhYWQ0ZjI0ZTczNjQyZDkzNA', + }, + }, + }, + manufacturer: 'Canon', + name: 'ACK-E2', + purchasable: true, + stock: { + isValueRounded: false, + stockLevel: 0, + stockLevelStatus: 'outOfStock', + }, + url: '/Open-Catalogue/Components/Power-Supplies/Power-Adapters-%26-Inverters/ACK-E2/p/514518', + }; + return { products: [product] }; + } + + private getMockCartData(): Cart { + const cart: Cart = { + appliedOrderPromotions: [ + { + consumedEntries: [], + description: 'Buy over $200.00 get $20.00 discount on cart', + promotion: { + code: 'order_threshold_fixed_discount_main', + promotionType: 'Rule Based Promotion', + }, + }, + ], + appliedProductPromotions: [], + appliedVouchers: [], + code: '00001021', + deliveryItemsQuantity: 2, + entries: [ + { + basePrice: { + formattedValue: '$315.52', + value: 315.52, + }, + cancellableQuantity: 0, + configurationInfos: [], + entryNumber: 0, + product: { + availableForPickup: true, + baseOptions: [], + categories: [ + { + code: '827', + name: 'Power Adapters & Inverters', + }, + { + code: 'brand_10', + name: 'Canon', + }, + ], + code: '514518', + configurable: false, + images: { + PRIMARY: { + zoom: { + altText: 'ACK-E2', + format: 'zoom', + imageType: ImageType.PRIMARY, + url: '/medias/?context=bWFzdGVyfGltYWdlc3wxNTYzNHxpbWFnZS9qcGVnfGltYWdlcy9oMjQvaGNkLzg3OTcyMTk3ODI2ODYuanBnfDRjMDljZjZkMjlhYTM5ODE3Y2IwYTBmN2VlNjdiMGY1YjljYmVlYjA1YzI5MDNhNTRjODU5MTkyNDVhMzVjODg', + }, + product: { + altText: 'ACK-E2', + format: 'product', + imageType: ImageType.PRIMARY, + url: '/medias/?context=bWFzdGVyfGltYWdlc3w4MDk0fGltYWdlL2pwZWd8aW1hZ2VzL2hhOC9oMjEvODc5NzI0NjA2MjYyMi5qcGd8MDFkMTI2YzBkMTk4N2I5ZjkwNjkzY2U3NGM5YjE4ZDg1ODhiNmMyMDIyYzViYzhiNTVmYjYzYzhhYzZkZDlkNw', + }, + thumbnail: { + altText: 'ACK-E2', + format: 'thumbnail', + imageType: ImageType.PRIMARY, + url: '/medias/?context=bWFzdGVyfGltYWdlc3wyMjk5fGltYWdlL2pwZWd8aW1hZ2VzL2hhMi9oZDMvODc5NzI3MjQwODA5NC5qcGd8OWMyMDViNzE4YmIwODU1MDZiMjc5NWI1NzdlNWMyZThiZDUxY2ZkNTRiYTM5MjY2NmI4ZGY3NTJiZDFjMTRjMw', + }, + cartIcon: { + altText: 'ACK-E2', + format: 'cartIcon', + imageType: ImageType.PRIMARY, + url: '/medias/?context=bWFzdGVyfGltYWdlc3wxNTI5fGltYWdlL2pwZWd8aW1hZ2VzL2g0Yi9oOTYvODc5NzI5ODc1MzU2Ni5qcGd8OWEwMjgwOTA4ZTg0MGI5NjgzYzU3MDUyY2MwMmIwMzY2NzJjMmJmM2M2M2JlNzJhYWQ0ZjI0ZTczNjQyZDkzNA', + }, + }, + }, + manufacturer: 'Canon', + name: 'ACK-E2 long name long name long name long name long name', + purchasable: true, + stock: { + isValueRounded: false, + stockLevel: 316, + stockLevelStatus: 'inStock', + }, + url: '/Open-Catalogue/Components/Power-Supplies/Power-Adapters-%26-Inverters/ACK-E2/p/514518', + }, + quantity: 1, + returnableQuantity: 0, + statusSummaryList: [], + totalPrice: { + currencyIso: 'USD', + formattedValue: '$315.52', + value: 315.52, + }, + updateable: true, + }, + { + basePrice: { + formattedValue: '$204.75', + value: 204.75, + }, + cancellableQuantity: 0, + configurationInfos: [], + entryNumber: 1, + product: { + availableForPickup: true, + baseOptions: [], + categories: [ + { + code: '814', + name: 'Rechargeable Batteries', + }, + { + code: 'brand_5', + name: 'Sony', + }, + ], + code: '3965240', + configurable: false, + images: { + PRIMARY: { + zoom: { + altText: 'NP-FV 70', + format: 'zoom', + imageType: ImageType.PRIMARY, + url: '/medias/?context=bWFzdGVyfGltYWdlc3wyMjg4MXxpbWFnZS9qcGVnfGltYWdlcy9oOTMvaDM1Lzg3OTcyMTYxNzgyMDYuanBnfGQ2OWI3NzI3MzNiODg0ZWZlMjc0NzQ0MzFjZjk4ZGU1ZDkzMmZjODU3MThkYTZiYTg2N2E0NTYyOTE2NGQyZjA', + }, + product: { + altText: 'INP-FV 70', + format: 'product', + imageType: ImageType.PRIMARY, + url: '/medias/?context=bWFzdGVyfGltYWdlc3w5MTQxfGltYWdlL2pwZWd8aW1hZ2VzL2hhOC9oYzgvODc5NzI0MjQ1ODE0Mi5qcGd8ZDAyOTdjOTJlNjU3NmJiNDE4ZjFiN2EzYmFjNTUwMDk2OTE4M2VlNjMwNzBmZmUzZjliZGY0NDdiYWU5N2VlYw', + }, + thumbnail: { + altText: 'INP-FV 70', + format: 'thumbnail', + imageType: ImageType.PRIMARY, + url: '/medias/?context=bWFzdGVyfGltYWdlc3wyMjI2fGltYWdlL2pwZWd8aW1hZ2VzL2gwYi9oNGUvODc5NzI2ODgwMzYxNC5qcGd8ZjM4MzcyYmYyZjc0NDBmYWVhYTMzZjVmYjZjYzc3ZTlkMWE4MDk2NmY2YzA3YTVjNTkyYjE2MTM2M2EyYjM2Yw', + }, + cartIcon: { + altText: 'INP-FV 70l', + format: 'cartIcon', + imageType: ImageType.PRIMARY, + url: '/medias/?context=bWFzdGVyfGltYWdlc3wxNTY5fGltYWdlL2pwZWd8aW1hZ2VzL2hjMC9oMjQvODc5NzI5NTE0OTA4Ni5qcGd8ZWUxNjg5NTIxMzM5MjBkMTFhNjQ3ODMyODRiMjRjNGI1ODg0OWM1ZTRlZjMzZWE2ZDcyNDExZmZlN2U4MTlhMg', + }, + }, + }, + manufacturer: 'Sony', + name: 'NP-FV 70', + purchasable: true, + stock: { + isValueRounded: false, + stockLevel: 300, + stockLevelStatus: 'inStock', + }, + url: '/Open-Catalogue/Components/Power-Supplies/Rechargeable-Batteries/NP-FV-70/p/3965240', + }, + quantity: 1, + returnableQuantity: 0, + statusSummaryList: [], + totalPrice: { + currencyIso: 'USD', + formattedValue: '$204.75', + value: 204.75, + }, + updateable: true, + }, + ], + guid: 'dea34e50-278f-4693-a308-14121cf2be37', + net: false, + pickupItemsQuantity: 0, + productDiscounts: { + formattedValue: '$0.00', + }, + subTotal: { + currencyIso: 'USD', + formattedValue: '$500.27', + priceType: PriceType.BUY, + value: 500.27, + }, + totalDiscounts: { + currencyIso: 'USD', + formattedValue: '$20.00', + priceType: PriceType.BUY, + value: 20.0, + }, + totalItems: 2, + totalPrice: { + currencyIso: 'USD', + formattedValue: '$500.27', + priceType: PriceType.BUY, + value: 500.27, + }, + totalPriceWithTax: { + currencyIso: 'USD', + formattedValue: '$500.27', + priceType: PriceType.BUY, + value: 500.27, + }, + totalTax: { + currencyIso: 'USD', + formattedValue: '$0.00', + priceType: PriceType.BUY, + value: 0.0, + }, + user: { + name: 'Hak Kim', + uid: 'kimhakwo@hotmail.com', + }, + paymentType: { + code: 'CARD', + displayName: 'Card Payment', + }, + potentialOrderPromotions: [], + potentialProductPromotions: [], + totalUnitCount: 2, + }; + return cart; + } +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.model.ts new file mode 100644 index 00000000000..ceb67733d31 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.model.ts @@ -0,0 +1,5 @@ +import { Product } from "@spartacus/core"; + +export interface AsmInterestEntry { + products: Array; +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.html new file mode 100644 index 00000000000..f782ae241cf --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.html @@ -0,0 +1,26 @@ + + + +
+ {{ + product?.name + }} +
{{ product?.code }}
+
{{ price?.formattedValue }}
+
+ {{ 'asm.customerOverview.outOfStock' | cxTranslate }} +
+
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.spec.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.spec.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.ts new file mode 100644 index 00000000000..dd7f5f66b69 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.ts @@ -0,0 +1,19 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { OrderEntry } from '@spartacus/cart/base/root'; +import { Price, Product } from '@spartacus/core'; + +@Component({ + selector: 'cx-asm-product-item', + templateUrl: './asm-product-item.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AsmProductItemComponent { + @Input() item: OrderEntry; + @Input() product: Product; + @Input() price: Price; + @Input() isOrderEntry = true; + @Output() selectProduct = new EventEmitter(); + + constructor() {} + +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.html new file mode 100644 index 00000000000..d3f7356f355 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.html @@ -0,0 +1,88 @@ +
+ +
+
+
+
+ {{ 'asm.profileAddresses.billingAddress' | cxTranslate }} +
+
+ +
+
+
+
+ {{ 'asm.profileAddresses.deliveryAddress' | cxTranslate }} +
+
+ +
+
+
+
+
+ {{ 'asm.profileAddresses.phone1' | cxTranslate }} +
+
+ {{ customerProfileData?.phone1 }} +
+
+
+
+ {{ 'asm.profileAddresses.phone2' | cxTranslate }} +
+
+ {{ customerProfileData?.phone2 }} +
+
+
+
+
+ {{ 'asm.profileAddresses.paymentMethodHeader' | cxTranslate }} +
+
+
+ +
+
+
+ +
+
+ {{ address?.line1 }} +
+
+ {{ address?.line2 }} +
+
+ {{ address?.town }} + {{ address?.region?.isocode }} + {{ address?.country?.isocode }} +
+
+ {{ address?.postalCode }} +
+
+
+
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.spec.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.spec.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.ts new file mode 100644 index 00000000000..2bdb11358be --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.ts @@ -0,0 +1,286 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { AsmConfig } from '@spartacus/asm/core'; + +import { Address, PaymentDetails, TranslationService, User } from '@spartacus/core'; +import { + BREAKPOINT, + BreakpointService, + Card, + FocusConfig, + ICON_TYPE, + ModalService, +} from '@spartacus/storefront'; + +import { combineLatest, forkJoin, of, Observable, Subscription } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { CustomerProfileData } from './asm-customer-profile.model'; + +@Component({ + selector: 'cx-asm-customer-profile', + templateUrl: './asm-customer-profile.component.html', +}) +export class AsmCustomerProfileComponent implements OnInit { + @Input() customer: User; + + focusConfig: FocusConfig = { + trap: true, + block: true, + autofocus: 'customer-list-selector', + focusOnEscape: true, + }; + + iconTypes = ICON_TYPE; + + BREAKPOINT = BREAKPOINT; + + breakpoint$: Observable; + + // customerListConfig = this.asmConfig?.asm?.customerList; + + customerProfileData$: Observable; + + protected subscription = new Subscription(); + + constructor( + protected modalService: ModalService, + protected breakpointService: BreakpointService, + protected asmConfig: AsmConfig, + protected translation: TranslationService + ) { + this.breakpoint$ = this.getBreakpoint(); + } + + ngOnInit(): void { + this.getSamplePaymentMethods(); + + if (this.customer?.uid) { + this.customerProfileData$ = forkJoin([ + of(this.getSamplePaymentMethods()), + of(this.getAddresses()), + ]).pipe( + map(([paymentDetails, addresses]) => { + const defaultPaymentDetail = paymentDetails.find( + (paymentDetail) => paymentDetail.defaultPayment + ); + const deliveryAddress = addresses.find( + (address) => address.defaultAddress + ); + return { + billingAddress: defaultPaymentDetail?.billingAddress, + deliveryAddress: deliveryAddress, + phone1: deliveryAddress?.phone, + phone2: deliveryAddress?.cellphone, + paymentInfoList: paymentDetails, + }; + }) + ); + } + } + + getCardContent({ + defaultPayment, + expiryMonth, + expiryYear, + cardNumber, + cardType, + }: PaymentDetails): Observable { + return combineLatest([ + this.translation.translate('paymentCard.expires', { + month: expiryMonth, + year: expiryYear, + }), + this.translation.translate('paymentCard.defaultPaymentMethod'), + ]).pipe( + map(([textExpires, textDefaultPaymentMethod]) => { + const card: Card = { + role: 'region', + header: defaultPayment ? textDefaultPaymentMethod : undefined, + text: [cardNumber ?? '', textExpires], + img: this.getCardIcon(cardType?.code ?? ''), + label: defaultPayment + ? 'paymentCard.defaultPaymentLabel' + : 'paymentCard.additionalPaymentLabel', + }; + + return card; + }) + ); + } + + getCardIcon(code: string): string { + let ccIcon: string; + if (code === 'visa') { + ccIcon = this.iconTypes.VISA; + } else if (code === 'master' || code === 'mastercard_eurocard') { + ccIcon = this.iconTypes.MASTER_CARD; + } else if (code === 'diners') { + ccIcon = this.iconTypes.DINERS_CLUB; + } else if (code === 'amex') { + ccIcon = this.iconTypes.AMEX; + } else { + ccIcon = this.iconTypes.CREDIT_CARD; + } + return ccIcon; + } + + private getBreakpoint(): Observable { + return this.breakpointService.breakpoint$.pipe( + map((breakpoint) => { + if (breakpoint === BREAKPOINT.lg || breakpoint === BREAKPOINT.xl) { + breakpoint = BREAKPOINT.md; + } + return breakpoint; + }) + ); + } + + private getSamplePaymentMethods(): PaymentDetails[] { + const paymentMethods: PaymentDetails[] = [ + { + accountHolderName: 'hak', + billingAddress: { + country: { + isocode: 'US', + }, + defaultAddress: false, + email: 'kimhakwo@hotmail.com', + firstName: 'billingFirst', + formattedAddress: + '53 State St Billing, Billing 2nd line, Massachusetts, Boston, 02109', + id: '8796158951447', + lastName: 'billingLast', + line1: '53 State St Billing, Billing 2nd line', + phone: '14165053687', + postalCode: '02109', + region: { + isocode: 'US-MA', + }, + town: 'Boston', + }, + cardNumber: '************1111', + cardType: { + code: 'visa', + name: 'Visa', + }, + defaultPayment: true, + expiryMonth: '3', + expiryYear: '2030', + id: '8796125921322', + saved: true, + subscriptionId: 'f009b2cf-3ac4-4763-a7ce-80c9e0c98f25', + }, + { + accountHolderName: 'hakMaster', + billingAddress: { + country: { + isocode: 'US', + }, + defaultAddress: false, + email: 'kimhakwo@hotmail.com', + firstName: 'US', + formattedAddress: '53 State St, , Massachusetts, Boston, 02109', + id: '8796159311895', + lastName: 'Hak', + line1: '53 State St, ', + phone: '14165053687', + postalCode: '02109', + region: { + isocode: 'US-MA', + }, + town: 'Boston', + }, + cardNumber: '************4444', + cardType: { + code: 'master', + name: 'Mastercard', + }, + defaultPayment: false, + expiryMonth: '3', + expiryYear: '2030', + id: '8796126052394', + saved: true, + subscriptionId: 'd90bb4b2-0bd3-4620-986f-43e2d062afe0', + }, + { + accountHolderName: 'hakMaster', + billingAddress: { + country: { + isocode: 'US', + }, + defaultAddress: false, + email: 'kimhakwo@hotmail.com', + firstName: 'US', + formattedAddress: '27 Corvus Starway, , Massachusetts, Boston, 02109', + id: '8796159311895', + lastName: 'Hak', + line1: '27 Corvus Starway, ', + phone: '14165053687', + postalCode: '02109', + region: { + isocode: 'US-MA', + }, + town: 'Boston', + }, + cardNumber: '************1234', + cardType: { + code: 'master', + name: 'Mastercard', + }, + defaultPayment: false, + expiryMonth: '3', + expiryYear: '2030', + id: '8796126052394', + saved: true, + subscriptionId: 'd90bb4b2-0bd3-4620-986f-43e2d062afe0', + }, + ]; + + return paymentMethods; + } + + private getAddresses(): Address[] { + const addresses = [ + { + country: { + isocode: 'US', + }, + defaultAddress: true, + firstName: 'US', + formattedAddress: '53 State St,, , Massachusetts, Boston, 02109', + id: '8796158918679', + lastName: 'Hak', + line1: '53 State St,', + line2: '', + phone: '14165053687', + postalCode: '02109', + region: { + isocode: 'US-MA', + }, + titleCode: 'mr', + town: 'Boston', + }, + { + country: { + isocode: 'US', + }, + defaultAddress: false, + firstName: 'FirstName1', + formattedAddress: + '53 State St Address line2,, Address line2, Massachusetts, Boston, 02109', + id: '8796159344663', + lastName: 'LastName1', + line1: '53 State St Address line2,', + line2: 'Address line2', + phone: '14165053687', + postalCode: '02109', + region: { + isocode: 'US-MA', + }, + titleCode: 'mr', + town: 'Boston', + }, + ]; + + return addresses; + } +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.model.ts new file mode 100644 index 00000000000..075abe2685f --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.model.ts @@ -0,0 +1,9 @@ +import { Address, PaymentDetails } from "@spartacus/core"; + +export interface CustomerProfileData { + billingAddress?: Address; + deliveryAddress?: Address; + phone1?: String; + phone2?: String; + paymentInfoList?: PaymentDetails[]; +}; diff --git a/feature-libs/asm/styles/components/_asm-customer-overview.scss b/feature-libs/asm/styles/components/_asm-customer-overview.scss new file mode 100644 index 00000000000..4eed3ced2b6 --- /dev/null +++ b/feature-libs/asm/styles/components/_asm-customer-overview.scss @@ -0,0 +1,90 @@ +%cx-asm-customer-overview { + width: 100%; + .cx-overview-title-link { + color: var(--cx-color-dark); + text-decoration: none; + cursor: pointer; + &:hover { + color: var(--cx-color-primary); + } + } + .cx-asm-overview-header-infor { + display: flex; + } + cx-asm-product-item { + display: flex; + } + .cx-asm-overview-items { + display: flex; + flex-wrap: wrap; + a { + cursor: pointer; + } + .cx-overview-product-name { + @include type('5'); + color: var(--cx-color-dark); + text-decoration: none; + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: inline-block; + } + &:hover { + color: var(--cx-color-primary); + } + } + .cx-overview-out-of-stock { + font-weight: var(--cx-font-weight-normal); + color: var(--cx-color-secondary); + } + } + .cx-asm-overview-order-items { + gap: 1rem; + cx-asm-product-item { + border: 1px solid var(--cx-color-light); + width: 300px; + cx-media { + width: 100px; + } + .asm-product-item-content { + padding: 1rem; + span { + width: 180px; + } + } + } + } + + .cx-asm-overview-interests-items { + text-align: center; + gap: 1rem; + cx-asm-product-item { + width: 150px; + flex-direction: column; + row-gap: 0.5rem; + cx-media { + max-width: 150px; + height: 150px; + } + .asm-product-item-content { + span { + width: 150px; + } + } + } + } + + .cx-overview-empty { + .cx-overview-empty-title { + @include type('5'); + font-weight: var(--cx-font-weight-normal); + line-height: var(--cx-line-height, 1.5); + border-bottom: 1px solid var(--cx-color-light); + } + .cx-overview-empty-desc { + font-size: var(--cx-font-size, 0.875rem); + font-weight: var(--cx-font-weight-normal); + } + } +} diff --git a/feature-libs/asm/styles/components/_asm-customer-profile.scss b/feature-libs/asm/styles/components/_asm-customer-profile.scss new file mode 100644 index 00000000000..7067f112fd7 --- /dev/null +++ b/feature-libs/asm/styles/components/_asm-customer-profile.scss @@ -0,0 +1,47 @@ +%cx-asm-customer-profile { + .table { + thead { + th { + text-transform: none; + } + } + td { + vertical-align: top; + } + tr { + border: 0; + } + } + + + .cx-asm-profile-address-cell { + display: flex; + flex-direction: column; + } + + .cx-card-img-container { + .fab, + .fas { + &::before { + font-size: var(--cx-font-size, 48px); + } + } + } + .cx-asm-profile-header { + font-size: 0.875rem; + font-weight: 700; + padding: 15px 0; + } + .cx-asm-profile-cards { + display: flex; + justify-content: flex-start; + flex-wrap: wrap; + .cx-asm-profile-card { + width: 300px; + padding-top: 0; + padding-inline-end: 15px; + padding-bottom: 15px; + padding-inline-start: 0; + } + } +} diff --git a/feature-libs/asm/styles/components/_index.scss b/feature-libs/asm/styles/components/_index.scss index 2a23477916b..0b1970b2951 100644 --- a/feature-libs/asm/styles/components/_index.scss +++ b/feature-libs/asm/styles/components/_index.scss @@ -1,3 +1,4 @@ +@import '~@spartacus/styles/scss/core'; @import './_asm-main-ui.component.scss'; @import './_asm-session-timer.component.scss'; @import './_asm-toggle-ui.component.scss'; @@ -7,10 +8,14 @@ @import './_customer-list.component.scss'; @import './_asm-bind-cart.component.scss'; @import './_dot-spinner.component.scss'; +@import './_asm-customer-360.component.scss'; +@import './_asm-customer-profile.scss'; +@import './_asm-customer-overview.scss'; $asm-components-allowlist: cx-asm-main-ui, cx-asm-session-timer, cx-asm-bind-cart, cx-asm-toggle-ui, cx-csagent-login-form, - cx-customer-emulation, cx-customer-selection, cx-dot-spinner, cx-customer-list !default; + cx-customer-emulation, cx-customer-selection, cx-dot-spinner, cx-customer-list, + cx-asm-customer-360, cx-asm-customer-profile, cx-asm-customer-overview !default; $skipComponentStyles: () !default; diff --git a/projects/storefrontstyles/scss/_versioning.scss b/projects/storefrontstyles/scss/_versioning.scss index 06ac3f15506..2dbeeebfc63 100644 --- a/projects/storefrontstyles/scss/_versioning.scss +++ b/projects/storefrontstyles/scss/_versioning.scss @@ -35,7 +35,7 @@ $styleVersion: $_majorVersion !default; // For Spartacus development or demo's, the usage of latest styles is recommended. // To avoid frequent changes of the $minorVersion in the application, we offer a // flag that can be used to keep up with the latest version at any time. -$useLatestStyles: false !default; +$useLatestStyles: true !default; // The `forVersion` mixin is used during development of the style layer. Breaking style // changes can be added for a specific version, and customers can only use those styles if From c99593a053df361c27483757fa541b25ae9690e3 Mon Sep 17 00:00:00 2001 From: Justin Lee Date: Mon, 18 Jul 2022 14:31:53 -0400 Subject: [PATCH 002/175] WIP: 360 Maps tab prototype - UI and SCSS. Next commit is to mock data --- .../asm-main-ui/asm-main-ui.component.html | 1 + .../prototype/prototype.component.html | 60 +++++++++ .../prototype/prototype.component.scss | 121 ++++++++++++++++++ .../prototype/prototype.component.spec.ts | 25 ++++ .../prototype/prototype.component.ts | 15 +++ 5 files changed, 222 insertions(+) create mode 100644 feature-libs/asm/components/asm-main-ui/prototype/prototype.component.html create mode 100644 feature-libs/asm/components/asm-main-ui/prototype/prototype.component.scss create mode 100644 feature-libs/asm/components/asm-main-ui/prototype/prototype.component.spec.ts create mode 100644 feature-libs/asm/components/asm-main-ui/prototype/prototype.component.ts diff --git a/feature-libs/asm/components/asm-main-ui/asm-main-ui.component.html b/feature-libs/asm/components/asm-main-ui/asm-main-ui.component.html index 14449e4533b..07e1f9fba48 100644 --- a/feature-libs/asm/components/asm-main-ui/asm-main-ui.component.html +++ b/feature-libs/asm/components/asm-main-ui/asm-main-ui.component.html @@ -59,6 +59,7 @@ > + 1 - 5 from 5 stores found + +
+
+
+
Boston
+
53 STATE ST, BOSTON, MA 02109 FLOOR 16
+
Boston
+
+
2 miles
+
+
+
+
Burlington
+
15 WAYSIDE RD, BURLINGTON, MA 01803
+
Burlington
+
+
5 miles
+
+
+
+
Palo Alto
+
3410 HILLVIEW AVENUE, PALO ALTO, CA 94304, USA
+
Palo Alto
+
+
200 miles
+
+
+ +
+
+ +
+
Boston
+
53 State St, Boston, MA 02109
+
Floor 16
+
Boston
+
+
+ +
+
+
Mon - Sat09:00 - 20:00
+
Sun09:00 - 17:00
+
+
+
Features
+
Wheelchair accessible
+
Taco Tuesday
+
+
+
diff --git a/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.scss b/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.scss new file mode 100644 index 00000000000..ca663658968 --- /dev/null +++ b/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.scss @@ -0,0 +1,121 @@ +::ng-deep .modal-dialog { + max-width: none !important; +} + +:host { + display: inline-grid; + font-size: 13px; + grid-gap: 12px; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: auto 1fr; + margin: 0 auto; + padding: 12px; + width: max-content; +} + +.bold { + font-weight: 600; +} + +.store-count { + grid-column: 1 / 3; +} + +.store-details { + display: flex; + flex-direction: row; + margin-bottom: 12px; + + &-info { + margin-left: 8px; + } +} + +.store-listing-item { + cursor: pointer; + display: flex; + flex-direction: row; + position: relative; + + &.selected { + background: #96bbe1; + color: white; + + &::after { + align-self: center; + border: 14px solid transparent; + border-right-color: white;; + content: ''; + display: block; + height: 28px; + position: absolute; + right: 0; + width: 28px; + } + } + + &:not(.selected) { + &:hover { + background: #d2dbe6; + } + + &:not(:hover) { + &:nth-of-type(2n+1) { + background: #eef3f7; + } + + &:nth-of-type(2n) { + background: white; + } + } + } + + &-details { + padding: 0 8px; + } + + &-distance { + align-items: center; + border-left: 1px solid #dce1e8; + box-sizing: border-box; + display: flex; + justify-content: center; + padding: 0 4px; + margin-left: auto; + width: 100px; + } + + &-text { + text-transform: uppercase; + } +} + +.store-map { + border: 0; + border-bottom: 1px solid #eeeeee; + border-top: 1px solid #eeeeee; + padding: 12px 0; +} + +.store-openings { + font-size: 14px; + + &-date { + align-items: center; + display: flex; + flex-direction: row; + } + + &-dates { + margin-bottom: 24px; + } + + &-day { + display: inline-block; + margin-right: 4px; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 60px; + } +} diff --git a/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.spec.ts b/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.spec.ts new file mode 100644 index 00000000000..6aa7e6ab6ce --- /dev/null +++ b/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PrototypeComponent } from './prototype.component'; + +describe('PrototypeComponent', () => { + let component: PrototypeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PrototypeComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PrototypeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.ts b/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.ts new file mode 100644 index 00000000000..f6792ab5a1c --- /dev/null +++ b/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'cx-prototype', + templateUrl: './prototype.component.html', + styleUrls: ['./prototype.component.scss'] +}) +export class PrototypeComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} From fc6c25d884fd61800885b4bfd5028931f682f3c9 Mon Sep 17 00:00:00 2001 From: Justin Lee Date: Mon, 25 Jul 2022 15:52:50 -0400 Subject: [PATCH 003/175] Adding MapData type for Map tab and using Angular directives to represent component data --- .../asm/components/asm-components.module.ts | 3 + .../asm-main-ui/prototype/map-types.ts | 20 +++++ .../prototype/prototype.component.html | 47 ++++-------- .../prototype/prototype.component.module.ts | 18 +++++ .../prototype/prototype.component.ts | 76 ++++++++++++++++++- 5 files changed, 130 insertions(+), 34 deletions(-) create mode 100644 feature-libs/asm/components/asm-main-ui/prototype/map-types.ts create mode 100644 feature-libs/asm/components/asm-main-ui/prototype/prototype.component.module.ts diff --git a/feature-libs/asm/components/asm-components.module.ts b/feature-libs/asm/components/asm-components.module.ts index 6b87d347b47..fd25def7759 100644 --- a/feature-libs/asm/components/asm-components.module.ts +++ b/feature-libs/asm/components/asm-components.module.ts @@ -32,6 +32,7 @@ import { AsmCustomerOverviewComponent } from './asm-customer-360/asm-customer-ov import { AsmProductItemComponent } from './asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component'; import { AsmCustomerProfileComponent } from './asm-customer-360/asm-customer-profile/asm-customer-profile.component'; import { AsmMainUiComponent } from './asm-main-ui/asm-main-ui.component'; +import { PrototypeComponent } from './asm-main-ui/prototype/prototype.component'; import { AsmSessionTimerComponent } from './asm-session-timer/asm-session-timer.component'; import { FormatTimerPipe } from './asm-session-timer/format-timer.pipe'; import { AsmToggleUiComponent } from './asm-toggle-ui/asm-toggle-ui.component'; @@ -78,6 +79,7 @@ import { DotSpinnerComponent } from './dot-spinner/dot-spinner.component'; AsmCustomerProfileComponent, AsmCustomerOverviewComponent, AsmProductItemComponent + PrototypeComponent, ], exports: [ AsmMainUiComponent, @@ -90,6 +92,7 @@ import { DotSpinnerComponent } from './dot-spinner/dot-spinner.component'; AsmToggleUiComponent, AsmBindCartComponent, DotSpinnerComponent, + PrototypeComponent, ], providers: [ provideConfig(defaultAsmLayoutConfig), diff --git a/feature-libs/asm/components/asm-main-ui/prototype/map-types.ts b/feature-libs/asm/components/asm-main-ui/prototype/map-types.ts new file mode 100644 index 00000000000..95e8c87381f --- /dev/null +++ b/feature-libs/asm/components/asm-main-ui/prototype/map-types.ts @@ -0,0 +1,20 @@ +export interface StoreData { + id: string; + name: string; + displayName: string; + line1: string; + line2: string; + town: string; + formattedDistance: string; + latitude: number; + longitude: number; + image: string; + productcode: string; + openings: { [opening: string]: string }, + features: Array; +} + +export interface MapData { + total: number; + data: Array; +} diff --git a/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.html b/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.html index 3ec7618ad15..d54a48dbd1a 100644 --- a/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.html +++ b/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.html @@ -1,40 +1,24 @@ -
1 - 5 from 5 stores found
+
1 - 5 from {{ storeData.total }} stores found
-
+
-
Boston
-
53 STATE ST, BOSTON, MA 02109 FLOOR 16
-
Boston
+
{{ store.displayName }}
+
{{ store.line1 }}, {{ store.line2 }}
+
{{ store.town }}
-
2 miles
-
-
-
-
Burlington
-
15 WAYSIDE RD, BURLINGTON, MA 01803
-
Burlington
-
-
5 miles
-
-
-
-
Palo Alto
-
3410 HILLVIEW AVENUE, PALO ALTO, CA 94304, USA
-
Palo Alto
-
-
200 miles
+
{{ store.formattedDistance }}
- +
-
Boston
-
53 State St, Boston, MA 02109
-
Floor 16
-
Boston
+
{{ selectedStore.displayName }}
+
{{ selectedStore.line1 }}
+
{{ selectedStore.line2 }}
+
{{ selectedStore.town }}
diff --git a/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.ts b/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.ts index 2cc8f6305bb..10392483688 100644 --- a/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.ts +++ b/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.ts @@ -1,8 +1,10 @@ -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { MapData, StoreData } from './map-types'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, selector: 'cx-prototype', templateUrl: './prototype.component.html', styleUrls: ['./prototype.component.scss'], @@ -77,7 +79,17 @@ export class PrototypeComponent implements OnInit { selectedStore: StoreData; - constructor() { + get encodedStoreLocation(): string { + return encodeURIComponent(this.selectedStore.line1); + } + + get googleMapsUrl(): SafeResourceUrl { + return this.sanitizer.bypassSecurityTrustResourceUrl( + `https://www.google.com/maps/embed/v1/place?key=AIzaSyAEwnpFNr0duKCE0DClFE7RRJJ9zUmJ8u8&q=${ this.encodedStoreLocation }¢er=${this.selectedStore.latitude},${this.selectedStore.longitude}&zoom=10` + ); + } + + constructor(protected sanitizer: DomSanitizer) { this.selectedStore = this.storeData.data[0]; } From 20abdd1203b216389b9f12f875c2d5e4d752df1d Mon Sep 17 00:00:00 2001 From: Justin Lee Date: Mon, 25 Jul 2022 18:13:31 -0400 Subject: [PATCH 005/175] Google Maps working fully, directing the current device from their location to the store --- .../prototype/prototype.component.html | 8 ++-- .../prototype/prototype.component.ts | 46 +++++++++++++++---- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.html b/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.html index 2b026fe47df..19585d56ba4 100644 --- a/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.html +++ b/feature-libs/asm/components/asm-main-ui/prototype/prototype.component.html @@ -1,8 +1,8 @@
1 - 5 from {{ storeData.total }} stores found
-
+
{{ store.displayName }}
{{ store.line1 }}, {{ store.line2 }}
@@ -24,8 +24,8 @@
-
- {{ opening.key }}{{ opening.value }} +
+ {{ opening.key }}{{ opening.value }}
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.module.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.module.ts index 7d2518de7ed..94967abcbb8 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.module.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.module.ts @@ -4,15 +4,8 @@ import { NgModule } from '@angular/core'; import { AsmCustomerMapComponent } from './asm-customer-map.component'; @NgModule({ - imports: [ - CommonModule, - ], - declarations: [ - AsmCustomerMapComponent, - ], - exports: [ - AsmCustomerMapComponent, - ], + imports: [CommonModule], + declarations: [AsmCustomerMapComponent], + exports: [AsmCustomerMapComponent], }) -export class AsmCustomerMapComponentModule { -} +export class AsmCustomerMapComponentModule {} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.scss b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.scss index 67dc487f117..7a710a992f4 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.scss +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.scss @@ -1,117 +1,117 @@ :host { - display: inline-grid; - font-size: 13px; - grid-gap: 12px; - grid-template-columns: repeat(2, 1fr); - grid-template-rows: auto 1fr; - margin: 0 auto; - width: 100%; + display: inline-grid; + font-size: 13px; + grid-gap: 12px; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: auto 1fr; + margin: 0 auto; + width: 100%; } .bold { - font-weight: 600; + font-weight: 600; } .store-count { - grid-column: 1 / 3; + grid-column: 1 / 3; } .store-details { - display: flex; - flex-direction: row; - margin-bottom: 12px; + display: flex; + flex-direction: row; + margin-bottom: 12px; - &-info { - margin-left: 8px; - } + &-info { + margin-left: 8px; + } } .store-listing-item { - cursor: pointer; - display: flex; - flex-direction: row; - position: relative; - - &.selected { - background: #96bbe1; - color: white; - - &::after { - align-self: center; - border: 14px solid transparent; - border-right-color: white;; - content: ''; - display: block; - height: 28px; - position: absolute; - right: 0; - width: 28px; - } + cursor: pointer; + display: flex; + flex-direction: row; + position: relative; + + &.selected { + background: #96bbe1; + color: white; + + &::after { + align-self: center; + border: 14px solid transparent; + border-right-color: white; + content: ''; + display: block; + height: 28px; + position: absolute; + right: 0; + width: 28px; } + } - &:not(.selected) { - &:hover { - background: #d2dbe6; - } - - &:not(:hover) { - &:nth-of-type(2n+1) { - background: #eef3f7; - } - - &:nth-of-type(2n) { - background: white; - } - } + &:not(.selected) { + &:hover { + background: #d2dbe6; } - &-details { - padding: 0 8px; - } + &:not(:hover) { + &:nth-of-type(2n + 1) { + background: #eef3f7; + } - &-distance { - align-items: center; - border-left: 1px solid #dce1e8; - box-sizing: border-box; - display: flex; - justify-content: center; - padding: 0 4px; - margin-left: auto; - width: 100px; + &:nth-of-type(2n) { + background: white; + } } + } - &-text { - text-transform: uppercase; - } + &-details { + padding: 0 8px; + } + + &-distance { + align-items: center; + border-left: 1px solid #dce1e8; + box-sizing: border-box; + display: flex; + justify-content: center; + padding: 0 4px; + margin-left: auto; + width: 100px; + } + + &-text { + text-transform: uppercase; + } } .store-map { - border: 0; - border-bottom: 1px solid #eeeeee; - border-top: 1px solid #eeeeee; - padding: 12px 0; - width: 100%; + border: 0; + border-bottom: 1px solid #eeeeee; + border-top: 1px solid #eeeeee; + padding: 12px 0; + width: 100%; } .store-openings { - font-size: 14px; + font-size: 14px; - &-date { - align-items: center; - display: flex; - flex-direction: row; - } - - &-dates { - margin-bottom: 24px; - } - - &-day { - display: inline-block; - margin-right: 4px; - overflow-x: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: 60px; - } + &-date { + align-items: center; + display: flex; + flex-direction: row; + } + + &-dates { + margin-bottom: 24px; + } + + &-day { + display: inline-block; + margin-right: 4px; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 60px; + } } diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.spec.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.spec.ts index b3a3d8dbc38..b8412b353ed 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.spec.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.spec.ts @@ -8,9 +8,8 @@ describe('AsmCustomerMapComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ AsmCustomerMapComponent ] - }) - .compileComponents(); + declarations: [AsmCustomerMapComponent], + }).compileComponents(); }); beforeEach(() => { diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.ts index 1be2bfda9a6..4418217523a 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.ts @@ -1,5 +1,10 @@ import { HttpParams } from '@angular/common/http'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnInit, +} from '@angular/core'; import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { MapData, StoreData } from './map-types'; @@ -8,7 +13,7 @@ import { MapData, StoreData } from './map-types'; changeDetection: ChangeDetectionStrategy.OnPush, selector: 'cx-asm-customer-map', templateUrl: './asm-customer-map.component.html', - styleUrls: ['./asm-customer-map.component.scss'] + styleUrls: ['./asm-customer-map.component.scss'], }) export class AsmCustomerMapComponent implements OnInit { storeData: MapData = { @@ -24,15 +29,14 @@ export class AsmCustomerMapComponent implements OnInit { formattedDistance: '2 miles', latitude: 42.358856201171875, longitude: -71.05696868896484, - image: 'https://s36700.pcdn.co/wp-content/uploads/2015/05/dachshund-puppies-03.jpg.optimal.jpg', + image: + 'https://s36700.pcdn.co/wp-content/uploads/2015/05/dachshund-puppies-03.jpg.optimal.jpg', productcode: 'productcode 1', openings: { 'Mon - Sat': '09:00 - 20:00', - 'Sun': '09:00 - 17:00', + Sun: '09:00 - 17:00', }, - features: [ - 'Wheelchair accessible', - ], + features: ['Wheelchair accessible'], }, { id: '2', @@ -44,15 +48,14 @@ export class AsmCustomerMapComponent implements OnInit { formattedDistance: '5 miles', latitude: 42.485267639160156, longitude: -71.19204711914062, - image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTWApY7vpCoiyrYKL1FUsfNDwYUSNPTG5TZlQ&usqp=CAU', + image: + 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTWApY7vpCoiyrYKL1FUsfNDwYUSNPTG5TZlQ&usqp=CAU', productcode: 'productcode 2', openings: { 'Mon - Sat': '09:00 - 20:00', - 'Sun': '09:00 - 17:00', + Sun: '09:00 - 17:00', }, - features: [ - 'Wheelchair accessible', - ], + features: ['Wheelchair accessible'], }, { id: '3', @@ -64,16 +67,14 @@ export class AsmCustomerMapComponent implements OnInit { formattedDistance: '200 miles', latitude: 37.4443293, longitude: -122.1598465, - image: 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/golden-retreiver-puppy-appears-on-nbc-news-today-show-on-news-photo-164288748-1551895571.jpg?crop=0.85948xw:1xh;center,top&resize=480:*', + image: + 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/golden-retreiver-puppy-appears-on-nbc-news-today-show-on-news-photo-164288748-1551895571.jpg?crop=0.85948xw:1xh;center,top&resize=480:*', productcode: 'productcode 3', openings: { 'Mon - Sat': '09:00 - 20:00', - 'Sun': '09:00 - 17:00', + Sun: '09:00 - 17:00', }, - features: [ - 'Wheelchair accessible', - 'Taco Tuesday', - ], + features: ['Wheelchair accessible', 'Taco Tuesday'], }, ], }; @@ -92,7 +93,7 @@ export class AsmCustomerMapComponent implements OnInit { } ngOnInit(): void { - navigator.geolocation.getCurrentPosition(position => { + navigator.geolocation.getCurrentPosition((position) => { this.currentLocation = `${position.coords.latitude},${position.coords.longitude}`; this.updateGoogleMapsUrl(); diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-map/map-types.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-map/map-types.ts index 95e8c87381f..e5114a37eea 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-map/map-types.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-map/map-types.ts @@ -10,7 +10,7 @@ export interface StoreData { longitude: number; image: string; productcode: string; - openings: { [opening: string]: string }, + openings: { [opening: string]: string }; features: Array; } diff --git a/feature-libs/asm/components/customer-emulation/customer-emulation.component.html b/feature-libs/asm/components/customer-emulation/customer-emulation.component.html index d4e90a058fa..902578bc2df 100644 --- a/feature-libs/asm/components/customer-emulation/customer-emulation.component.html +++ b/feature-libs/asm/components/customer-emulation/customer-emulation.component.html @@ -14,7 +14,12 @@
- + diff --git a/feature-libs/asm/components/customer-emulation/customer-emulation.component.ts b/feature-libs/asm/components/customer-emulation/customer-emulation.component.ts index 2b6edc7b8fb..e4014daedec 100644 --- a/feature-libs/asm/components/customer-emulation/customer-emulation.component.ts +++ b/feature-libs/asm/components/customer-emulation/customer-emulation.component.ts @@ -40,6 +40,15 @@ export class CustomerEmulationComponent implements OnInit, OnDestroy { this.asmComponentService.logoutCustomer(); } + openCustomer360() { + /* + this.modalRef = this.modalService?.open(AsmCustomer360Component, { + size: 'xl', + }); + this.modalRef.componentInstance.customer = this.customer; + */ + } + ngOnDestroy(): void { this.subscription.unsubscribe(); } diff --git a/feature-libs/asm/styles/components/_asm-customer-360.component.scss b/feature-libs/asm/styles/components/_asm-customer-360.component.scss index 5188d543116..2749975dea9 100644 --- a/feature-libs/asm/styles/components/_asm-customer-360.component.scss +++ b/feature-libs/asm/styles/components/_asm-customer-360.component.scss @@ -13,7 +13,7 @@ padding-bottom: 3rem; display: flex; .cx-avatar { - font-size:56px; + font-size: 56px; width: 110px; height: 110px; line-height: 100px; @@ -42,7 +42,7 @@ .close { font-size: 1rem; color: var(--cx-color-dark); - top:-100px; + top: -100px; } } .cx-dialog-body { @@ -62,7 +62,7 @@ } .cx-tab { padding: 10px 20px; - color: var(--cx-color-light);; + color: var(--cx-color-light); border-bottom: 1px solid #dddddd; &:hover { border-color: #556b82; @@ -71,11 +71,11 @@ text-decoration: none; } &:active, - &:focus{ + &:focus { color: var(--cx-color-visual-focus); border-width: 0 0 2px; text-decoration: none; - border-color: var(--cx-color-visual-focus) ; + border-color: var(--cx-color-visual-focus); } } .cx-tab-content { @@ -92,7 +92,7 @@ color: var(--cx-color-visual-focus); border-width: 0 0 2px; text-decoration: none; - border-color: var(--cx-color-visual-focus) ; + border-color: var(--cx-color-visual-focus); } } } diff --git a/feature-libs/asm/styles/components/_customer-emulation.component.scss b/feature-libs/asm/styles/components/_customer-emulation.component.scss index 5e9b7d775bd..2dc6f36465c 100644 --- a/feature-libs/asm/styles/components/_customer-emulation.component.scss +++ b/feature-libs/asm/styles/components/_customer-emulation.component.scss @@ -60,6 +60,17 @@ width: 100%; } } + + .cx-360-button { + background-color: #0070f2; + border: 1px solid #0070f2; + color: #ffffff; + } + .cx-logout-button { + color: #aa0808; + background-color: #ffd6ea; + border: 1px solid #ffd6ea; + margin-left: 15px; } @include forVersion(5.1) { From ae0fccc70f1d2cfae65b5b884bde6058c6f59b52 Mon Sep 17 00:00:00 2001 From: Lee Date: Tue, 2 Aug 2022 14:39:59 -0400 Subject: [PATCH 008/175] Fixing bug where selected item was not highlighted. Adding pagination UI but not used for Map tab --- .../asm-customer-map.component.html | 7 +++++- .../asm-customer-map.component.scss | 23 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.html index e5afc292af2..ce094285ac3 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.html +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.html @@ -1,9 +1,14 @@
1 - 5 from {{ storeData.total }} stores found
+ +
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.scss b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.scss index 7a710a992f4..715021cc40c 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.scss +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.scss @@ -12,8 +12,29 @@ font-weight: 600; } +.pagination { + margin-left: auto; + + &-arrow { + background: none; + border: none; + color: #0486e0; + font-weight: 600; + padding: 12px; + + &:hover { + opacity: 0.3; + } + + &-right { + margin-left: 8px; + } + } +} + +.pagination, .store-count { - grid-column: 1 / 3; + align-self: center; } .store-details { From 22040ae30b953a12770eafd7245b347f1eeba3f3 Mon Sep 17 00:00:00 2001 From: Jeremy Laverdiere Date: Thu, 4 Aug 2022 15:43:49 -0400 Subject: [PATCH 009/175] Created listing and table UI components --- .../asm-customer-listing.component.html | 81 +++++++++ .../asm-customer-listing.component.ts | 10 ++ .../asm-customer-listing.model.ts | 41 +++++ .../asm-customer-table.component.html | 52 ++++++ .../asm-customer-table.component.ts | 10 ++ .../asm-customer-table.model.ts | 127 +++++++++++++ .../_asm-customer-listing.component.scss | 167 ++++++++++++++++++ .../_asm-customer-table.component.scss | 114 ++++++++++++ 8 files changed, 602 insertions(+) create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.html create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.model.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.html create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.model.ts create mode 100644 feature-libs/asm/styles/components/_asm-customer-listing.component.scss create mode 100644 feature-libs/asm/styles/components/_asm-customer-table.component.scss diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.html new file mode 100644 index 00000000000..770a486c9eb --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.html @@ -0,0 +1,81 @@ +
+ {{ fragment.text }} +
+
+ + + + + + + + + +
+ +
+ {{ tab.tabName }} +
+
+
+ + + + + + +
+ + + +
+ + +
+
+ +
+
{{ item.title }}
+
+
{{ item.description }}
+
+ +
+ +
+ {{ item.appliedText }} +
+ +
+ | +
+ {{ item.buttonText + }} +
+
+
+
+
+
+
+
{{ emptyText }}
+
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.ts new file mode 100644 index 00000000000..7dfc44a3c7f --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.ts @@ -0,0 +1,10 @@ +import { Component, Input } from '@angular/core'; +import { ListingFragmentType } from './asm-customer-listing.model'; + +@Component({ + selector: 'cx-asm-customer-listing', + templateUrl: './asm-customer-listing.component.html', +}) +export class AsmCustomerListingComponent { + @Input() fragment: ListingFragmentType; +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.model.ts new file mode 100644 index 00000000000..f0c55dbfca1 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.model.ts @@ -0,0 +1,41 @@ +import { Class, Fragment } from '../../asm-customer-360.model'; + +export abstract class ListingFragment implements Fragment { + abstract type: string; + abstract text: string; + items: Array; + emptyText?: string; + buttonAction?(item: ListingItem): void; + + constructor( + rawItems: Array, + itemClass: Class + ) { + this.items = rawItems.map((item) => new itemClass(item)); + } +} + +export interface Searchable extends ListingFragment { + searchTerm: string; + searchAction(term: string): void; +} + +export interface Tabulated extends ListingFragment { + currentTab: number; + tabs: Array<{ + tabName: string; + items: Array; + emptyText: string; + }>; + setCurrentTab(tab: number): void; +} + +export type ListingFragmentType = ListingFragment & Searchable & Tabulated; + +export class ListingItem { + [key: string]: string | boolean | undefined; +} + +export interface RawListingItem { + [key: string]: string | boolean | number | undefined; +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.html new file mode 100644 index 00000000000..06c68033978 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.html @@ -0,0 +1,52 @@ +
+
+ {{ fragment.text }} +
+
+ +
+ {{ pageNumber + 1 }} +
+
+
+
+
+ + + + + + + + +
+ {{ column.text }} +
+ {{ + entry[column.property].text || + entry[column.property].value || + '---' + }} + + + + + {{ + entry[column.property].text || + entry[column.property].value || + '---' + }} + +
+
+
{{fragment.emptyText}}
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.ts new file mode 100644 index 00000000000..20503fe9bc1 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.ts @@ -0,0 +1,10 @@ +import { Component, Input } from "@angular/core"; +import { TableFragment } from "./asm-customer-table.model"; + +@Component({ + selector: 'cx-asm-customer-table', + templateUrl: './asm-customer-table.component.html', +}) +export class AsmCustomerTableComponent { + @Input() fragment: TableFragment; +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.model.ts new file mode 100644 index 00000000000..f219a5a9a6c --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.model.ts @@ -0,0 +1,127 @@ + +import { Fragment, Class } from '../../asm-customer-360.model'; +import { keyValuePair } from "../../asm-customer-360.model"; + +export abstract class TableFragment implements Fragment { + entries: Array; + columns: Array<{ + text: string; + property: string; + sortOrder?: 'asc' | 'desc'; + }>; + currentSort: { + property: string; + sortOrder: 'asc' | 'desc'; + }; + entryPages: Array>; + currentPage: number; + pageSize: number; + + abstract emptyText: string; + abstract type: string; + abstract text: string; + + public setPageNumber(pageNumber: number): void { + this.currentPage = pageNumber; + } + + public sortEntriesAndUpdatePages(sortByProperty: string): void { + this.entries = this.sortEntries(this.entries, sortByProperty); + this.entryPages = this.updateEntryPages(this.entries); + } + + private updateEntryPages( + entries: Array + ): Array> { + const newEntryPages = []; + for (let i = 0; i < entries.length; i += this.pageSize) { + newEntryPages.push(entries.slice(i, i + this.pageSize)); + } + return newEntryPages; + } + + private sortEntries( + entries: Array, + sortByProperty: string + ): Array { + if (sortByProperty === this.currentSort?.property) { + if (this.currentSort.sortOrder === 'asc') { + this.currentSort.sortOrder = 'desc'; + } else if (this.currentSort.sortOrder === 'desc') { + this.currentSort.sortOrder = 'asc'; + } + } else if (this.currentSort?.property !== sortByProperty) { + this.currentSort.property = sortByProperty; + this.currentSort.sortOrder = 'asc'; + } + const newEntries = entries.slice(); + return newEntries.sort((entryA, entryB) => { + const prev = entryA[sortByProperty]?.value; + const next = entryB[sortByProperty]?.value; + let dir = 1; + if (entryA[sortByProperty]?.details?.reverseSort) { + dir = -1; + } + if (this.currentSort.sortOrder === 'asc') { + if (prev !== undefined && next !== undefined) { + return prev < next ? dir * -1 : dir * 1; + } else { + return prev !== undefined ? -1 : 1; + } + } else { + if (prev !== undefined && next !== undefined) { + return prev < next ? dir * 1 : dir * -1; + } else { + return prev !== undefined ? -1 : 1; + } + } + }); + } + + constructor( + rawEntries: Array, + sortProperty: string, + entryClass: Class, + pageSize: number = 5, + currentPage: number = 0 + ) { + this.currentPage = currentPage; + this.pageSize = pageSize; + this.currentSort = { property: sortProperty, sortOrder: 'desc' }; + this.entries = this.sortEntries( + rawEntries.map((rawEntry) => new entryClass(rawEntry)), + this.currentSort.property + ); + this.entryPages = this.updateEntryPages(this.entries); + this.columns = Object.getOwnPropertyNames(new entryClass({})).map((key) => { + let column: { + property: string; + text: string; + sortOrder?: 'asc' | 'desc'; + } = { property: key, text: key.toUpperCase() }; + if (key === this.currentSort.property) { + column.sortOrder = this.currentSort.sortOrder; + } + return column; + }); + } +} + +export interface RawTableEntry { + [key: string]: string | Array | number | undefined; +} + +export class TableEntry { + [key: string]: TableEntryCell; +} + +export interface TableEntryCell { + value?: string | number; + text?: string; + details?: { + url?: string; + starRating?: boolean; + reverseSort?: boolean; + }; +} + diff --git a/feature-libs/asm/styles/components/_asm-customer-listing.component.scss b/feature-libs/asm/styles/components/_asm-customer-listing.component.scss new file mode 100644 index 00000000000..e8773d5428b --- /dev/null +++ b/feature-libs/asm/styles/components/_asm-customer-listing.component.scss @@ -0,0 +1,167 @@ +%cx-asm-customer-listing { + .listing { + &-header { + font-size: 22px; + font-family: Dosis, sans-serif; + font-weight: 500; + width: 100%; + padding-top: 50px; + padding-bottom: 14px; + text-align: center; + } + + &-separator { + border: none; + border-bottom: 1px solid #d4d7dc; + margin: 0; + } + + &-empty { + font-size: 15px; + padding-top: 14px; + } + + &-item { + &-text { + padding: 26px 0 26px 30px; + } + + &-title { + margin-bottom: 3px; + font-weight: bold; + font-size: 17px; + color: #313440; + } + + &-subtitle { + display: flex; + } + + &-description { + font-size: 15px; + color: #313440; + width: 100%; + } + + &-status { + flex: none; + width: 250px; + line-height: 24px; + text-align: right; + + button { + font-family: Dosis, sans-serif; + font-size: 18px; + padding: 10px 35px; + background: #3f5875; + border: none; + color: #ffffff; + flex: none; + margin-top: -6px; + } + } + + &-applied { + display: flex; + white-space: nowrap; + font-size: 13px; + justify-content: flex-end; + margin-top: 1px; + + cx-icon { + background: radial-gradient(#18bc33 50%, #ffffff 0); + color: #ffffff; + font-size: 20px; + margin-right: 5px; + } + + &-text { + color: #18bc33; + } + + &-text-button { + all: unset; + color: #ff3a35; + text-decoration: none; + cursor: pointer; + user-select: none; + } + + &-pipe { + padding-left: 15px; + padding-right: 15px; + color: #d5dee8; + } + } + } + + &-tab { + &-container { + display: flex; + margin-top: 14px; + border-bottom: solid 1px #dddddd; + } + + &-item { + padding: 10px 15px; + color: #a1aebd; + font-weight: bold; + cursor: pointer; + font-size: 14px; + margin-right: 1px; + margin-bottom: -1px; + + &.active, &:hover, &:focus { + color: #0486e0; + border-bottom: solid; + border-width: 2px; + } + + &:hover { + border-bottom-color: #dddddd; + } + + &.active { + border-bottom-color: #0486e0; + cursor: default; + } + } + } + + &-input-group { + display: flex; + border: 1px solid #cccccc; + margin-top: 16px; + height: 44px; + + button { + all: unset; + } + + input { + width: 100%; + height: 100%; + border: none; + outline: none; + border-radius: 0; + box-shadow: none; + padding-left: 8px; + } + + cx-icon { + height: 100%; + width: 44px; + display: flex; + justify-content: center; + align-items: center; + color: #ec7205; + cursor: pointer; + font-size: 16px; + + &:hover { + color: #154259; + } + } + } + } +} diff --git a/feature-libs/asm/styles/components/_asm-customer-table.component.scss b/feature-libs/asm/styles/components/_asm-customer-table.component.scss new file mode 100644 index 00000000000..745c91b7ffe --- /dev/null +++ b/feature-libs/asm/styles/components/_asm-customer-table.component.scss @@ -0,0 +1,114 @@ +%cx-asm-customer-table { + .table { + width: 100%; + border-collapse: collapse; + table-layout: fixed; + margin-top: 14px; + + &-heading { + width: 100%; + font-size: 16px; + justify-content: space-between; + display: flex; + color: #19212b; + + &-text { + padding-top: 30px; + padding-bottom: 7px; + } + + &-pages { + display: flex; + font-size: 14px; + align-self: flex-end; + } + + &-page { + color: #0486e0; + cursor: pointer; + padding-left: 14px; + padding-right: 14px; + font-weight: bold; + + &.active { + color: #7f90a4; + cursor: default; + } + } + } + + &-separator { + border: none; + border-bottom: 1px solid #d4d7dc; + margin: 0; + } + + &-empty { + padding-top: 14px; + } + + &-row { + border: none; + border-top: 1px solid #e5e5e5; + + &-header { + border: none; + } + + &:nth-child(2n + 2) { + background: #eef3f7; + } + } + + th, td { + text-align: left; + vertical-align: top; + padding: 10px; + } + + td { + color: #313440; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 15px; + + a { + font-weight: bold; + color: #0486e0; + text-decoration: none; + } + } + + th { + font-size: 13px; + color: #7f90a4; + cursor: pointer; + user-select: none; + + &:hover, &.active { + color: #454a5d; + } + + &.active { + &.desc::after, &.asc::after { + content: ''; + margin-left: 8px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + position: relative; + } + + &.desc::after { + border-bottom: 5px solid #7f90a4; + bottom: 11px; + } + + &.asc::after { + border-top: 5px solid #7f90a4; + top: 13px; + } + } + } + } +} From 7a084500891eb7c314fa5706d8c1522a059cbaa1 Mon Sep 17 00:00:00 2001 From: Jeremy Laverdiere Date: Thu, 4 Aug 2022 15:46:36 -0400 Subject: [PATCH 010/175] Create activity, feedback, and promotios tabs based on listing and table ui components. --- .../asm-customer-360.utils.ts | 29 +++ .../asm-customer-activity.component.html | 1 + .../asm-customer-activity.component.ts | 110 +++++++++++ .../asm-customer-activity.model.ts | 77 ++++++++ .../asm-customer-feedback.component.html | 1 + .../asm-customer-feedback.component.ts | 146 ++++++++++++++ .../asm-customer-feedback.model.ts | 138 ++++++++++++++ .../asm-customer-promotions.component.html | 1 + .../asm-customer-promotions.component.ts | 82 ++++++++ .../asm-customer-promotions.model.ts | 180 ++++++++++++++++++ 10 files changed, 765 insertions(+) create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-360.utils.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.component.html create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.component.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.model.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.component.html create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.component.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.model.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.html create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.model.ts diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.utils.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-360.utils.ts new file mode 100644 index 00000000000..60801cd6dbe --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.utils.ts @@ -0,0 +1,29 @@ +import { keyValuePair } from "./asm-customer-360.model"; + +export function replaceTokens( + text?: string, + tokens?: Array +): string | undefined { + return tokens?.length + ? tokens.reduce((currentText, currentTokenValue) => { + return currentText?.replace( + `\${${currentTokenValue.key}}`, + currentTokenValue.value + ); + }, text) + : text; +} + +export function combineStrings( + string1?: string, + string2?: string, + delimiter: string = '' +): string | undefined { + return string1 || string2 + ? `${string1 || ''}${delimiter}${string2 || ''}` + : undefined; +} + +export function padTo2Digits(num: number) { + return num.toString().padStart(2, '0'); +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.component.html new file mode 100644 index 00000000000..32856a927ad --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.component.html @@ -0,0 +1 @@ + diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.component.ts new file mode 100644 index 00000000000..e69c4036019 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.component.ts @@ -0,0 +1,110 @@ +import { Component } from "@angular/core"; +import { GeneralFragment } from "./asm-customer-activity.model"; + +@Component({ + selector: 'cx-asm-customer-activity', + templateUrl: './asm-customer-activity.component.html', +}) +export class AsmCustomerActivityComponent { + rawGeneralFragment = { + type: 'general', + entries: [ + { + type: 'Ticket', + id: '00000001', + description: 'Thing not work good', + created: Number(new Date('2022-07-07T18:25:43.511Z')), + updated: Number(new Date('2022-07-07T18:25:43.511Z')), + category: 'New', + }, + { + type: 'Ticket', + id: '00000002', + description: 'Thing not work as expected', + created: Number(new Date('2022-07-03T18:25:43.511Z')), + updated: Number(new Date('2022-07-06T18:25:43.511Z')), + category: 'Closed', + }, + { + type: 'Ticket', + id: '00000003', + description: 'Thing work but so slow ${cool}', + descriptionArgs: [{ key: 'cool', value: 'beans' }], + created: Number(new Date('2022-06-30T18:25:43.511Z')), + updated: Number(new Date('2022-07-01T18:25:43.511Z')), + category: 'Addressed', + }, + { + type: 'Cart', + id: '00002001', + description: 'Cart with 1 item', + created: Number(new Date('2022-07-01T18:25:43.511Z')), + updated: Number(new Date('2022-07-02T18:25:43.511Z')), + url: 'https://www.example.com/00002001', + }, + { + type: 'Cart', + id: '00002004', + description: 'Cart with 2 items', + created: Number(new Date('2022-06-01T18:25:43.511Z')), + updated: Number(new Date('2022-07-06T18:25:43.511Z')), + url: 'https://www.example.com/00002004', + }, + { + type: 'Cart', + id: '00002007', + description: 'Cart with 0 items', + created: Number(new Date('2022-06-15T18:25:43.511Z')), + updated: Number(new Date('2022-06-20T18:25:43.511Z')), + url: 'https://www.example.com/00002007', + }, + { + type: 'Saved Cart', + id: '00002002', + description: 'Cart with 2 items', + created: Number(new Date('2022-07-02T18:25:43.511Z')), + updated: Number(new Date('2022-07-04T18:25:43.511Z')), + }, + { + type: 'Saved Cart', + id: '00002005', + description: 'Cart with 3 items', + created: Number(new Date('2022-06-09T18:25:43.511Z')), + updated: Number(new Date('2022-06-12T18:25:43.511Z')), + }, + { + type: 'Saved Cart', + id: '00002008', + description: 'Cart with 4 items', + created: Number(new Date('2022-06-22T18:25:43.511Z')), + updated: Number(new Date('2022-06-22T18:25:43.511Z')), + }, + { + type: 'Order', + id: '00002003', + description: 'Cart with 1 item', + created: Number(new Date('2022-05-30T18:25:43.511Z')), + updated: Number(new Date('2022-05-31T18:25:43.511Z')), + category: 'Draft', + }, + { + type: 'Order', + id: '00002006', + description: 'Cart with 2 items', + created: Number(new Date('2022-06-05T18:25:43.511Z')), + updated: Number(new Date('2022-06-06T18:25:43.511Z')), + category: 'Completed', + }, + { + type: 'Order', + id: '00002009', + description: 'Cart with 0 items', + created: Number(new Date('2022-05-15T18:25:43.511Z')), + updated: Number(new Date('2022-05-20T18:25:43.511Z')), + category: 'Processing', + }, + ], + }; + + uiFragment = new GeneralFragment(this.rawGeneralFragment.entries); +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.model.ts new file mode 100644 index 00000000000..ce617c62407 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.model.ts @@ -0,0 +1,77 @@ +import { TableFragment } from '../asm-customer-ui-components/asm-customer-table/asm-customer-table.model'; +import { formatDate } from '@angular/common'; +import { keyValuePair } from '../asm-customer-360.model'; +import { replaceTokens } from '../asm-customer-360.utils'; +import { + RawTableEntry, + TableEntry, + TableEntryCell, +} from '../asm-customer-ui-components/asm-customer-table/asm-customer-table.model'; + +export interface RawGeneralEntry extends RawTableEntry { + type?: string; + id?: string; + description?: string; + descriptionArgs?: Array; + category?: string; + created?: number; + updated?: number; + url?: string; +} + +export class GeneralEntry extends TableEntry { + type: TableEntryCell; + id: TableEntryCell; + description: TableEntryCell; + status: TableEntryCell; + created: TableEntryCell; + updated: TableEntryCell; + + constructor(entry: RawGeneralEntry) { + super(); + this.type = { + value: entry.type, + }; + this.id = { + value: entry.id, + details: { + url: entry.url, + reverseSort: true, + }, + }; + this.description = { + value: replaceTokens(entry.description, entry.descriptionArgs), + }; + this.status = { + value: entry.category, + }; + this.created = { + value: entry.created, + text: entry.created + ? formatDate(entry.created, 'dd-MM-yy hh:mm a', 'en-US') + : undefined, + details: { + reverseSort: true, + }, + }; + this.updated = { + value: entry.updated, + text: entry.updated + ? formatDate(entry.updated, 'dd-MM-yy hh:mm a', 'en-US') + : undefined, + details: { + reverseSort: true, + }, + }; + } +} + +export class GeneralFragment extends TableFragment { + type = 'general'; + text = 'General'; + emptyText = 'There is currently no recorded customer activity'; + + constructor(entries: Array) { + super(entries, 'updated', GeneralEntry, 10); + } +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.component.html new file mode 100644 index 00000000000..2b1ff94e57b --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.component.html @@ -0,0 +1 @@ + diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.component.ts new file mode 100644 index 00000000000..787c15085c2 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.component.ts @@ -0,0 +1,146 @@ +import { Component } from '@angular/core'; +import { TicketFragment, ReviewFragment } from './asm-customer-feedback.model'; + +@Component({ + selector: 'cx-asm-customer-feedback', + templateUrl: './asm-customer-feedback.component.html', +}) +export class AsmCustomerFeedbackComponent { + rawReviewFragment = { + type: 'reviews', + entries: [ + { + productName: 'DC Car Battery Adapter', + SKUNumber: '107701', + rating: 0.5, + reviewStatus: 'pending', + reviewText: 'Adapter? More like Badapter!!!', + productUrl: 'https://www.example.com/107701', + created: Number(new Date('2022-05-15T18:25:43.511Z')), + }, + { + productName: 'VCT-D580RM Remote Control Tripod', + SKUNumber: '2992', + rating: 4.5, + reviewStatus: 'pending', + reviewText: 'Flimsy stand!', + productUrl: 'https://www.example.com/2992', + created: Number(new Date('2022-06-22T18:25:43.511Z')), + }, + { + productName: 'Mini T-Cam', + SKUNumber: '458542', + rating: 1, + reviewStatus: 'pending', + reviewText: 'Webcam bad', + productUrl: 'https://www.example.com/458542', + created: Number(new Date('2022-07-02T18:25:43.511Z')), + }, + { + productName: 'HDR-CX105E Red', + SKUNumber: '1934406', + rating: 1.5, + reviewStatus: 'pending', + reviewText: 'First review', + productUrl: 'https://www.example.com/1934406', + created: Number(new Date('2022-07-03T18:25:43.511Z')), + }, + { + productName: 'DC Car Battery Adapter', + SKUNumber: '10770', + rating: 2.5, + reviewStatus: 'pending', + reviewText: 'Adapter? More like Badapter!!!', + productUrl: 'https://www.example.com/10770', + created: Number(new Date('2022-05-15T18:25:43.511Z')), + }, + { + productName: 'VCT-D580RM Remote Control Tripod', + SKUNumber: '29925', + rating: 3.5, + reviewStatus: 'pending', + reviewText: 'Flimsy stand!', + productUrl: 'https://www.example.com/29925', + created: Number(new Date('2022-06-22T18:25:43.511Z')), + }, + { + productName: 'Mini T-Cam', + SKUNumber: '4585', + rating: 4, + reviewStatus: 'pending', + reviewText: 'Webcam bad', + productUrl: 'https://www.example.com/4585', + created: Number(new Date('2022-07-02T18:25:43.511Z')), + }, + { + productName: 'HDR-CX105E Red', + SKUNumber: '19406', + rating: 3, + reviewStatus: 'pending', + reviewText: 'First review', + productUrl: 'https://www.example.com/19406', + created: Number(new Date('2022-07-03T18:25:43.511Z')), + }, + ], + }; + + rawTicketFragment = { + type: 'tickets', + entries: [ + { + type: 'Enquiry', + id: '00000001', + description: 'Can thing work this way instead?', + created: Number(new Date('2022-07-02T18:25:43.511Z')), + updated: Number(new Date('2022-07-04T18:25:43.511Z')), + category: 'New', + }, + { + type: 'Complaint', + id: '00000002', + description: 'Thing not work', + created: Number(new Date('2022-07-04T18:25:43.511Z')), + updated: Number(new Date('2022-07-05T18:25:43.511Z')), + category: 'Closed', + }, + { + type: 'Problem', + id: '00000003', + description: 'Thing work but super slow', + created: Number(new Date('2022-06-30T18:25:43.511Z')), + updated: Number(new Date('2022-07-01T18:25:43.511Z')), + category: 'Addressed', + }, + { + type: 'Enquiry', + id: '00000004', + description: 'Why thing work this way?', + created: Number(new Date('2022-05-30T18:25:43.511Z')), + updated: Number(new Date('2022-06-10T18:25:43.511Z')), + category: 'New', + }, + { + type: 'Complaint', + id: '00000005', + description: 'Thing work but so slow ${cool}', + descriptionArgs: [{ key: 'cool', value: 'as hell' }], + created: Number(new Date('2022-07-03T18:25:43.511Z')), + updated: Number(new Date('2022-07-06T18:25:43.511Z')), + category: 'Closed', + }, + { + type: 'Problem', + id: '00000006', + description: 'Thing not work as expected', + created: Number(new Date('2022-06-30T18:25:43.511Z')), + updated: Number(new Date('2022-07-01T18:25:43.511Z')), + category: 'Addressed', + }, + ], + }; + + uiFragments = [ + new TicketFragment(this.rawTicketFragment.entries), + new ReviewFragment(this.rawReviewFragment.entries), + ]; +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.model.ts new file mode 100644 index 00000000000..a07fb0ea5da --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.model.ts @@ -0,0 +1,138 @@ +import { formatDate } from '@angular/common'; +import { keyValuePair } from '../asm-customer-360.model'; +import { replaceTokens, combineStrings } from '../asm-customer-360.utils'; +import { RawTableEntry, TableEntry, TableEntryCell, TableFragment } from '../asm-customer-ui-components/asm-customer-table/asm-customer-table.model'; + +export interface RawTicketEntry extends RawTableEntry { + type?: string; + id?: string; + description?: string; + descriptionArgs?: Array; + category?: string; + created?: number; + updated?: number; + url?: string; +} + +export interface RawReviewEntry extends RawTableEntry { + productName?: string; + SKUNumber?: string; + rating?: number; + reviewStatus?: string; + reviewText?: string; + productUrl?: string; + created?: number; +} + +export class TicketEntry extends TableEntry { + id: TableEntryCell; + headline: TableEntryCell; + category: TableEntryCell; + status: TableEntryCell; + created: TableEntryCell; + updated: TableEntryCell; + + constructor(entry: RawTicketEntry) { + super(); + this.id = { + value: entry.id, + details: { + url: entry.url, + reverseSort: true, + }, + }; + this.headline = { + value: replaceTokens(entry.description, entry.descriptionArgs), + }; + this.category = { + value: entry.type, + }; + this.status = { + value: entry.category, + }; + this.created = { + value: entry.created, + text: entry.created + ? formatDate(entry.created, 'dd-MM-yy hh:mm a', 'en-US') + : undefined, + details: { + reverseSort: true, + }, + }; + this.updated = { + value: entry.updated, + text: entry.updated + ? formatDate(entry.updated, 'dd-MM-yy hh:mm a', 'en-US') + : undefined, + details: { + reverseSort: true, + }, + }; + } +} + +export class ReviewEntry extends TableEntry { + item: TableEntryCell; + dateAndStatus: TableEntryCell; + rate: TableEntryCell; + review: TableEntryCell; + + constructor(entry: RawReviewEntry) { + super(); + this.item = { + value: combineStrings(entry.productName, entry.SKUNumber), + text: combineStrings(entry.productName, entry.SKUNumber, ', SKU: '), + details: { + url: entry.productUrl, + }, + }; + this.dateAndStatus = { + value: combineStrings(entry.created?.toString(), entry.reviewStatus), + text: combineStrings( + entry.created + ? formatDate(entry.created, 'dd-MM-yy', 'en-US') + : undefined, + entry.reviewStatus, + ' / ' + ), + details: { + reverseSort: true, + }, + }; + this.rate = { + value: entry.rating, + details: { + starRating: true, + reverseSort: true, + }, + }; + this.review = { + value: entry.reviewText, + }; + } +} + +export class TicketFragment extends TableFragment { + type = 'tickets'; + text = 'Support Tickets'; + emptyText = 'There are currently no Support Ticket items'; + + constructor(entries: Array) { + super(entries, 'updated', TicketEntry); + } +} + +export class ReviewFragment extends TableFragment { + type = 'reviews'; + text = 'Product Reviews'; + emptyText = 'There are currently no Product Review items'; + + constructor(entries: Array) { + super(entries, 'dateAndStatus', ReviewEntry); + this.columns.forEach((column) => { + if (column.property === 'dateAndStatus') { + column.text = 'DATE / STATUS'; + } + }); + } +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.html new file mode 100644 index 00000000000..2a394ba179a --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.html @@ -0,0 +1 @@ + diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.ts new file mode 100644 index 00000000000..8c5338200ef --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.ts @@ -0,0 +1,82 @@ +import { Component } from "@angular/core"; +import { CouponFragment, PromotionFragment, CustomerCouponFragment } from "./asm-customer-promotions.model"; + +@Component({ + selector: 'cx-asm-customer-promotions', + templateUrl: './asm-customer-promotions.component.html', +}) +export class AsmCustomerPromotionsComponent { + rawCouponFragment = { + type: 'coupons', + items: [ + { + code: 'This is a 20% off coupon', + name: 'This is a 20% off coupon and you are gonna be so happy with these savings', + couponApplied: false, + }, + { + code: 'This is a 1% off coupon', + name: "This is a 1% off coupon and you won't really notice I'm here", + couponApplied: true, + }, + ], + }; + + rawPromotionFragment = { + type: 'promotions', + items: [ + { + name: 'This is a 20% off coupon', + firedMessage: 'This is a 20% off coupon and you are gonna be so happy with these savings', + fired: true, + }, + { + name: 'This is a 1% off coupon', + firedMessage: "This is a 1% off coupon and you won't really notice I'm here", + fired: false, + }, + ], + }; + + rawCustomerCouponFragment = { + type: 'customerCoupons', + items: [ + { + code: 'This is a 20% off coupon', + name: 'This is a 20% off coupon and you are gonna be so happy with these savings', + couponApplied: true, + }, + { + code: 'This is a 1% off coupon', + name: "This is a 1% off coupon and you won't really notice I'm here", + couponApplied: false, + }, + { + code: 'This is a 500% off coupon', + name: 'This is a 500% off coupon and you are gonna be so happy with these earnings', + couponApplied: false, + }, + { + code: 'This is a 0% off coupon', + name: "This is a 0% off coupon and you literally won't notice I'm here", + couponApplied: true, + }, + { + code: 'This is a 100% off coupon', + name: 'It\'s free, what else do you want?', + couponApplied: false, + }, + { + code: 'This is a 66.666666% off coupon', + name: "This is the devil\'s discount. By accepting this, you accept Satan as your lord and savior :)", + couponApplied: true, + }, + ], + }; + + uiFragments = [ + new CouponFragment(this.rawCouponFragment.items), + new PromotionFragment(this.rawPromotionFragment.items), + new CustomerCouponFragment(this.rawCustomerCouponFragment.items), + ]; +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.model.ts new file mode 100644 index 00000000000..a5ee6d1ad03 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.model.ts @@ -0,0 +1,180 @@ +import { of } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { + ListingFragment, + ListingItem, + RawListingItem, + Searchable, + Tabulated, +} from '../asm-customer-ui-components/asm-customer-listing/asm-customer-listing.model'; + +export interface RawPromotionItem extends RawListingItem { + code?: string; + name?: string; + couponApplied?: boolean; + + description?: string; + firedMessage?: string; + fired?: boolean; + priority?: number; +} + +export class CouponListingItem extends ListingItem { + title?: string; + description?: string; + applied?: boolean; + textButton?: boolean; + + get buttonText(): string { + return this.applied ? 'REMOVE' : 'APPLY TO CART'; + } + + get appliedText(): string | undefined { + return this.applied ? 'COUPON APPLIED' : undefined; + } + + constructor(item: RawPromotionItem) { + super(); + this.title = item.code; + this.description = item.name; + this.applied = item.couponApplied; + this.textButton = item.couponApplied; + } +} + +export class PromotionListingItem extends ListingItem { + title?: string; + description?: string; + applied?: boolean; + + get appliedText(): string | undefined { + return this.applied ? 'PROMOTION APPLIED' : undefined; + } + + constructor(item: RawPromotionItem) { + super(); + this.title = item.name; + this.description = item.firedMessage; + this.applied = item.fired; + } +} + +export class CustomerCouponListingItem extends ListingItem { + title?: string; + description?: string; + applied?: boolean; + + get buttonText(): string { + return this.applied ? 'REMOVE' : 'SEND TO CUSTOMER'; + } + + constructor(item: RawPromotionItem) { + super(); + this.title = item.code; + this.description = item.name; + this.applied = item.couponApplied; + } +} + +export class CouponFragment extends ListingFragment { + type = 'coupons'; + text = 'COUPONS'; + emptyText = 'There are currently no CSA Coupons available.'; + items: Array; + + buttonAction(item: CouponListingItem) { + of(undefined) + .pipe( + tap(() => { + item.applied = !item.applied; + item.textButton = item.applied; + }) + ) + .subscribe(); + } + + constructor(rawItems: Array) { + super(rawItems, CouponListingItem); + } +} + +export class PromotionFragment extends ListingFragment { + type = 'promotions'; + text = 'PROMOTIONS'; + emptyText = 'There are currently no CSA Promotions available.'; + items: Array; + + constructor(rawItems: Array) { + super(rawItems, PromotionListingItem); + } +} + +export class CustomerCouponFragment + extends ListingFragment + implements Tabulated, Searchable +{ + type = 'customerCoupons'; + text = 'CUSTOMER COUPONS'; + items: Array; + searchable: Searchable; + tabs: Tabulated['tabs']; + currentTab = 0; + searchTerm = ''; + + buttonAction(item: CustomerCouponListingItem) { + of(undefined) + .pipe( + tap(() => { + item.applied = !item.applied; + this.tabs = this.tabulate(this.items); + }) + ) + .subscribe(); + } + + searchAction(term: string): void { + if (term.length) { + const filteredItems = this.items.filter((item) => + item.description?.toLowerCase().includes(term.toLowerCase()) + ); + this.tabs = this.tabulate(filteredItems); + } else { + this.tabs = this.tabulate(this.items); + } + } + + setCurrentTab(tab: number): void { + this.currentTab = tab; + } + + private tabulate(items: Array): Tabulated['tabs'] { + const tabs: Tabulated['tabs'] = [ + { ...this.tabs[0], items: [] }, + { ...this.tabs[1], items: [] }, + ]; + + items.forEach((item) => { + const index = Number(item.applied); + tabs[index].items.push(item); + }); + + return tabs; + } + + constructor(rawItems: Array) { + super(rawItems, CustomerCouponListingItem); + this.tabs = [ + { + tabName: 'AVAILABLE', + items: [], + emptyText: 'There are currently no available customer coupons.', + }, + { + tabName: 'SENT', + items: [], + emptyText: 'There are currently no sent customer coupons.', + }, + ]; + this.tabs = this.tabulate(this.items); + } +} From 20c4d63c3447e6e22d3cd7dc14a935bb2be2fdb3 Mon Sep 17 00:00:00 2001 From: Jeremy Laverdiere Date: Thu, 4 Aug 2022 15:50:57 -0400 Subject: [PATCH 011/175] Add overview, profile, activity, feedback, and promotions to updated customer-360 template and module --- .../asm/components/asm-components.module.ts | 2 + .../asm-customer-360.component.html | 16 ++++-- .../asm-customer-360.component.module.ts | 51 +++++++++++++++++-- .../asm-customer-360.model.ts | 26 ++++++++-- 4 files changed, 82 insertions(+), 13 deletions(-) diff --git a/feature-libs/asm/components/asm-components.module.ts b/feature-libs/asm/components/asm-components.module.ts index 090f789a887..9d846981ca2 100644 --- a/feature-libs/asm/components/asm-components.module.ts +++ b/feature-libs/asm/components/asm-components.module.ts @@ -23,6 +23,7 @@ import { NgSelectA11yModule, MediaModule, PasswordVisibilityToggleModule, + PopoverModule, SortingModule, SpinnerModule, } from '@spartacus/storefront'; @@ -50,6 +51,7 @@ import { DotSpinnerComponent } from './dot-spinner/dot-spinner.component'; I18nModule, FormErrorsModule, IconModule, + PopoverModule, NgSelectModule, FormsModule, SpinnerModule, diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html index 97816d56d0f..3211d784207 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html @@ -32,10 +32,18 @@
-
- - - +
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.module.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.module.ts index b11a9849c0a..7ab65e2edf0 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.module.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.module.ts @@ -1,12 +1,55 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; - +import { RouterModule } from '@angular/router'; +import { I18nModule, UrlModule } from '@spartacus/core'; +import { + CardModule, + MediaModule, + StarRatingModule, +} from '@spartacus/storefront'; +import { AsmCustomerActivityComponent } from './asm-customer-activity/asm-customer-activity.component'; +import { AsmCustomerFeedbackComponent } from './asm-customer-feedback/asm-customer-feedback.component'; +import { AsmCustomerPromotionsComponent } from './asm-customer-promotions/asm-customer-promotions.component'; +import { AsmCustomerListingComponent } from './asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component'; +import { AsmCustomerTableComponent } from './asm-customer-ui-components/asm-customer-table/asm-customer-table.component'; import { AsmCustomer360Component } from './asm-customer-360.component'; import { AsmCustomerMapComponentModule } from './asm-customer-map/asm-customer-map.component.module'; +import { AsmCustomerOverviewComponent } from './asm-customer-overview/asm-customer-overview.component'; +import { AsmProductItemComponent } from './asm-customer-overview/asm-product-item/asm-product-item.component'; +import { AsmCustomerProfileComponent } from './asm-customer-profile/asm-customer-profile.component'; @NgModule({ - imports: [CommonModule, AsmCustomerMapComponentModule], - declarations: [AsmCustomer360Component], - exports: [AsmCustomer360Component], + imports: [ + CommonModule, + I18nModule, + CardModule, + MediaModule, + RouterModule, + UrlModule, + StarRatingModule, + AsmCustomerMapComponentModule, + ], + declarations: [ + AsmCustomer360Component, + AsmCustomerListingComponent, + AsmCustomerTableComponent, + AsmCustomerActivityComponent, + AsmCustomerFeedbackComponent, + AsmCustomerPromotionsComponent, + AsmCustomerProfileComponent, + AsmCustomerOverviewComponent, + AsmProductItemComponent, + ], + exports: [ + AsmCustomer360Component, + AsmCustomerListingComponent, + AsmCustomerTableComponent, + AsmCustomerActivityComponent, + AsmCustomerFeedbackComponent, + AsmCustomerPromotionsComponent, + AsmCustomerProfileComponent, + AsmCustomerOverviewComponent, + AsmProductItemComponent, + ], }) export class AsmCustomer360ComponentModule {} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-360.model.ts index 6eb6f83762a..ae05cec9c76 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-360.model.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.model.ts @@ -6,22 +6,38 @@ import { export const Customer360Sections: Customer360Section[] = [ { sectionTitle: CUSTOMER_360_SECTION_TITLE.OVERVIEW, - sectionContent: 'Placeholer content for Overview tab', + sectionContent: 'Placeholder content for Overview tab', }, { sectionTitle: CUSTOMER_360_SECTION_TITLE.PROFILE, - sectionContent: 'Placeholer content for Profile tab', + sectionContent: 'Placeholder content for Profile tab', }, { sectionTitle: CUSTOMER_360_SECTION_TITLE.ACTIVITY, - sectionContent: 'Placeholer content for Activity tab', + sectionContent: 'Placeholder content for Activity tab', }, { sectionTitle: CUSTOMER_360_SECTION_TITLE.FEEDBACK, - sectionContent: 'Placeholer content for Feedback tab', + sectionContent: 'Placeholder content for Feedback tab', + }, + { + sectionTitle: CUSTOMER_360_SECTION_TITLE.PROMOTIONS, + sectionContent: 'Placeholder content for Promotions tab', }, { sectionTitle: CUSTOMER_360_SECTION_TITLE.MAPS, - sectionContent: 'Placeholer content for Maps tab', + sectionContent: 'Placeholder content for Maps tab', }, ]; + +export interface Fragment { + type: string; + text: string; +} + +export interface keyValuePair { + key: string; + value: string; +} + +export type Class = new (arg: U) => T; From f95d7e59be3087120f7115832ec186220c7c1952 Mon Sep 17 00:00:00 2001 From: Jeremy Laverdiere Date: Thu, 4 Aug 2022 15:51:30 -0400 Subject: [PATCH 012/175] Make styles more consistent with accelerator --- .../asm-customer-360.component.ts | 14 +++-- feature-libs/asm/root/model/asm.models.ts | 35 +++++++++++ .../_asm-customer-360.component.scss | 58 +++++++++---------- .../asm/styles/components/_index.scss | 5 +- .../scss/theme/sparta/_variables.scss | 4 +- 5 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 feature-libs/asm/root/model/asm.models.ts diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.ts index 040edec0f09..fe13c2a70dd 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.ts @@ -10,17 +10,21 @@ import { Customer360Sections } from './asm-customer-360.model'; styles: [ ` ::ng-deep ngb-modal-window { - overflow-y: hidden !important; + padding: 20px 10%; } ::ng-deep .modal-dialog { display: flex; - max-height: 80vh !important; - max-width: 80vw !important; + width: 100%; + max-height: 100%; + min-height: unset; + min-width: unset !important; + max-width: unset !important; + margin: unset; } - ::ng-deep .modal-content { - max-height: 100%; + ::ng-deep .modal-body { + padding-top: 0; } `, ], diff --git a/feature-libs/asm/root/model/asm.models.ts b/feature-libs/asm/root/model/asm.models.ts new file mode 100644 index 00000000000..a7c3255cff9 --- /dev/null +++ b/feature-libs/asm/root/model/asm.models.ts @@ -0,0 +1,35 @@ +import { PaginationModel, SortModel, User } from '@spartacus/core'; + +export interface CustomerSearchPage { + entries: User[]; + pagination?: PaginationModel; + sorts?: SortModel[]; +} + +export interface CustomerSearchOptions { + query?: string; + pageSize?: number; +} + +export interface AsmUi { + collapsed?: boolean; +} + +export interface BindCartParams { + cartId: string; + customerId: string; +} + +export interface Customer360Section { + sectionTitle: CUSTOMER_360_SECTION_TITLE; + sectionContent: string; +} + +export enum CUSTOMER_360_SECTION_TITLE { + OVERVIEW = 'OVERVIEW', + PROFILE = 'PROFILE', + ACTIVITY = 'ACTIVITY', + FEEDBACK = 'FEEDBACK', + PROMOTIONS = 'PROMOTIONS', + MAPS = 'MAPS', +} diff --git a/feature-libs/asm/styles/components/_asm-customer-360.component.scss b/feature-libs/asm/styles/components/_asm-customer-360.component.scss index 2749975dea9..522e3d67375 100644 --- a/feature-libs/asm/styles/components/_asm-customer-360.component.scss +++ b/feature-libs/asm/styles/components/_asm-customer-360.component.scss @@ -50,7 +50,7 @@ flex: 1; flex-direction: column; padding-top: 0; - overflow-y: hidden; + overflow: auto; .cx-dialog-row { flex: none; } @@ -59,40 +59,38 @@ border-bottom: 1px solid #dddddd; margin-top: 20px; padding-bottom: 8px; - } - .cx-tab { - padding: 10px 20px; - color: var(--cx-color-light); - border-bottom: 1px solid #dddddd; - &:hover { - border-color: #556b82; - color: var(--cx-color-visual-focus); - border-width: 0 0 2px; - text-decoration: none; - } - &:active, - &:focus { - color: var(--cx-color-visual-focus); - border-width: 0 0 2px; - text-decoration: none; - border-color: var(--cx-color-visual-focus); + .cx-tab { + padding: 10px 20px; + color: var(--cx-color-light); + border-bottom: 1px solid #dddddd; + margin-right: 1px; + cursor: pointer; + &.active { + cursor: default; + } + &.active, + &:hover, + &:focus { + border-width: 0 0 2px; + padding: 10px 20px 9px 20px; + color: var(--cx-color-visual-focus); + text-decoration: none; + } + &:hover { + border-color: #556b82; + } } - } - .cx-tab-content { - flex: 1; - min-height: 0; - overflow-y: auto; - } - .active { - color: var(--cx-color-visual-focus); - border-width: 0 0 2px; - text-decoration: none; - border-color: var(--cx-color-visual-focus); - &:hover { + .active { color: var(--cx-color-visual-focus); border-width: 0 0 2px; text-decoration: none; border-color: var(--cx-color-visual-focus); + &:hover { + color: var(--cx-color-visual-focus); + border-width: 0 0 2px; + text-decoration: none; + border-color: var(--cx-color-visual-focus); + } } } } diff --git a/feature-libs/asm/styles/components/_index.scss b/feature-libs/asm/styles/components/_index.scss index 0b1970b2951..1a9672532f9 100644 --- a/feature-libs/asm/styles/components/_index.scss +++ b/feature-libs/asm/styles/components/_index.scss @@ -11,11 +11,14 @@ @import './_asm-customer-360.component.scss'; @import './_asm-customer-profile.scss'; @import './_asm-customer-overview.scss'; +@import './_asm-customer-table.component.scss'; +@import './_asm-customer-listing.component.scss'; $asm-components-allowlist: cx-asm-main-ui, cx-asm-session-timer, cx-asm-bind-cart, cx-asm-toggle-ui, cx-csagent-login-form, cx-customer-emulation, cx-customer-selection, cx-dot-spinner, cx-customer-list, - cx-asm-customer-360, cx-asm-customer-profile, cx-asm-customer-overview !default; + cx-asm-customer-360, cx-asm-customer-profile, + cx-asm-customer-overview, cx-asm-customer-table, cx-asm-customer-listing !default; $skipComponentStyles: () !default; diff --git a/projects/storefrontstyles/scss/theme/sparta/_variables.scss b/projects/storefrontstyles/scss/theme/sparta/_variables.scss index c405362abfa..2ba7f2a3d05 100644 --- a/projects/storefrontstyles/scss/theme/sparta/_variables.scss +++ b/projects/storefrontstyles/scss/theme/sparta/_variables.scss @@ -12,11 +12,11 @@ //fonts (see _fonts.scss to import) $font-family-sans-serif: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', - 'Segoe UI Emoji', 'Segoe UI Symbol'; + 'Segoe UI Emoji', 'Segoe UI Symbol', 'Dosis'; // TODO: Replace `$font-url` with swap url in 5.0 to use swap strategy by default. // $font-url: 'https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&display=swap' !default; -$font-url: 'https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700' !default; +$font-url: 'https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700|Dosis:400,300,500,600' !default; // change theme-colors here $primary: #fe5757 !default; From 434bf78b873b8f50f7559b8ee4cce23f41068cea Mon Sep 17 00:00:00 2001 From: Jeremy Laverdiere Date: Thu, 4 Aug 2022 20:27:41 -0400 Subject: [PATCH 013/175] Use module for each tab and ui components --- .../asm-customer-360.component.module.ts | 47 +++++-------------- .../asm-customer-activity.module.ts | 11 +++++ .../asm-customer-feedback.module.ts | 11 +++++ .../asm-customer-overview.module.ts | 14 ++++++ .../asm-customer-profile.module.ts | 11 +++++ .../asm-customer-promotions.module.ts | 11 +++++ .../asm-customer-listing.module.ts | 11 +++++ .../asm-customer-table.module.ts | 11 +++++ 8 files changed, 91 insertions(+), 36 deletions(-) create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.module.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.module.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.module.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.module.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.module.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.module.ts create mode 100644 feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.module.ts diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.module.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.module.ts index 7ab65e2edf0..b4b75af135d 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.module.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.module.ts @@ -1,55 +1,30 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { I18nModule, UrlModule } from '@spartacus/core'; -import { - CardModule, - MediaModule, - StarRatingModule, -} from '@spartacus/storefront'; -import { AsmCustomerActivityComponent } from './asm-customer-activity/asm-customer-activity.component'; -import { AsmCustomerFeedbackComponent } from './asm-customer-feedback/asm-customer-feedback.component'; -import { AsmCustomerPromotionsComponent } from './asm-customer-promotions/asm-customer-promotions.component'; -import { AsmCustomerListingComponent } from './asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component'; -import { AsmCustomerTableComponent } from './asm-customer-ui-components/asm-customer-table/asm-customer-table.component'; +import { I18nModule } from '@spartacus/core'; import { AsmCustomer360Component } from './asm-customer-360.component'; import { AsmCustomerMapComponentModule } from './asm-customer-map/asm-customer-map.component.module'; -import { AsmCustomerOverviewComponent } from './asm-customer-overview/asm-customer-overview.component'; -import { AsmProductItemComponent } from './asm-customer-overview/asm-product-item/asm-product-item.component'; -import { AsmCustomerProfileComponent } from './asm-customer-profile/asm-customer-profile.component'; +import { AsmCustomerActivityModule } from './asm-customer-activity/asm-customer-activity.module'; +import { AsmCustomerOverviewModule } from './asm-customer-overview/asm-customer-overview.module'; +import { AsmCustomerProfileModule } from './asm-customer-profile/asm-customer-profile.module'; +import { AsmCustomerFeedbackModule } from './asm-customer-feedback/asm-customer-feedback.module'; +import { AsmCustomerPromotionsModule } from './asm-customer-promotions/asm-customer-promotions.module'; @NgModule({ imports: [ CommonModule, I18nModule, - CardModule, - MediaModule, - RouterModule, - UrlModule, - StarRatingModule, + AsmCustomerOverviewModule, + AsmCustomerProfileModule, + AsmCustomerActivityModule, + AsmCustomerFeedbackModule, + AsmCustomerPromotionsModule, AsmCustomerMapComponentModule, ], declarations: [ AsmCustomer360Component, - AsmCustomerListingComponent, - AsmCustomerTableComponent, - AsmCustomerActivityComponent, - AsmCustomerFeedbackComponent, - AsmCustomerPromotionsComponent, - AsmCustomerProfileComponent, - AsmCustomerOverviewComponent, - AsmProductItemComponent, ], exports: [ AsmCustomer360Component, - AsmCustomerListingComponent, - AsmCustomerTableComponent, - AsmCustomerActivityComponent, - AsmCustomerFeedbackComponent, - AsmCustomerPromotionsComponent, - AsmCustomerProfileComponent, - AsmCustomerOverviewComponent, - AsmProductItemComponent, ], }) export class AsmCustomer360ComponentModule {} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.module.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.module.ts new file mode 100644 index 00000000000..7f9c76d52ad --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { AsmCustomerTableModule } from '../asm-customer-ui-components/asm-customer-table/asm-customer-table.module'; +import { AsmCustomerActivityComponent } from './asm-customer-activity.component'; + +@NgModule({ + imports: [CommonModule, AsmCustomerTableModule], + declarations: [AsmCustomerActivityComponent], + exports: [AsmCustomerActivityComponent], +}) +export class AsmCustomerActivityModule {} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.module.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.module.ts new file mode 100644 index 00000000000..05ee2f88c11 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-feedback/asm-customer-feedback.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { AsmCustomerTableModule } from '../asm-customer-ui-components/asm-customer-table/asm-customer-table.module'; +import { AsmCustomerFeedbackComponent } from './asm-customer-feedback.component'; + +@NgModule({ + imports: [CommonModule, AsmCustomerTableModule], + declarations: [AsmCustomerFeedbackComponent], + exports: [AsmCustomerFeedbackComponent], +}) +export class AsmCustomerFeedbackModule {} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.module.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.module.ts new file mode 100644 index 00000000000..bdbc7be1f76 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.module.ts @@ -0,0 +1,14 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { I18nModule, UrlModule } from '@spartacus/core'; +import { MediaModule } from '@spartacus/storefront'; +import { AsmCustomerOverviewComponent } from './asm-customer-overview.component'; +import { AsmProductItemComponent } from './asm-product-item/asm-product-item.component'; + +@NgModule({ + imports: [CommonModule, MediaModule, RouterModule, UrlModule, I18nModule], + declarations: [AsmCustomerOverviewComponent, AsmProductItemComponent], + exports: [AsmCustomerOverviewComponent], +}) +export class AsmCustomerOverviewModule {} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.module.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.module.ts new file mode 100644 index 00000000000..04b91f0ddab --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { I18nModule } from '@spartacus/core'; +import { AsmCustomerProfileComponent } from './asm-customer-profile.component'; + +@NgModule({ + imports: [CommonModule, I18nModule], + declarations: [AsmCustomerProfileComponent], + exports: [AsmCustomerProfileComponent], +}) +export class AsmCustomerProfileModule {} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.module.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.module.ts new file mode 100644 index 00000000000..95a4f2a963e --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { AsmCustomerListingModule } from '../asm-customer-ui-components/asm-customer-listing/asm-customer-listing.module'; +import { AsmCustomerPromotionsComponent } from './asm-customer-promotions.component'; + +@NgModule({ + imports: [CommonModule, AsmCustomerListingModule], + declarations: [AsmCustomerPromotionsComponent], + exports: [AsmCustomerPromotionsComponent], +}) +export class AsmCustomerPromotionsModule {} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.module.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.module.ts new file mode 100644 index 00000000000..cbf3d6fbe98 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { IconModule } from '@spartacus/storefront'; +import { AsmCustomerListingComponent } from './asm-customer-listing.component'; + +@NgModule({ + imports: [CommonModule, IconModule], + declarations: [AsmCustomerListingComponent], + exports: [AsmCustomerListingComponent], +}) +export class AsmCustomerListingModule {} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.module.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.module.ts new file mode 100644 index 00000000000..7d3c8966d53 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { StarRatingModule } from '@spartacus/storefront'; +import { AsmCustomerTableComponent } from './asm-customer-table.component'; + +@NgModule({ + imports: [CommonModule, StarRatingModule], + declarations: [AsmCustomerTableComponent], + exports: [AsmCustomerTableComponent], +}) +export class AsmCustomerTableModule {} From 93024391b09fe9b9f319406fe6fc3f55e1c92387 Mon Sep 17 00:00:00 2001 From: Hakwoo Kim Date: Fri, 5 Aug 2022 15:39:12 -0400 Subject: [PATCH 014/175] moved 360 dialog style --- .../asm-customer-360.component.html | 6 +++++- .../asm-customer-360.component.ts | 21 ------------------- .../customer-emulation.component.ts | 3 +++ .../scss/cxbase/blocks/modal.scss | 19 +++++++++++++++++ 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html index 3211d784207..7f3f3c0a0dc 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html @@ -6,13 +6,17 @@ {{ getAvatar() }}
-
{{ customer.uid }}
+
+ Customer 360° View +
+
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.ts index fe13c2a70dd..0b8b0adc12c 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.ts @@ -7,27 +7,6 @@ import { Customer360Sections } from './asm-customer-360.model'; @Component({ selector: 'cx-asm-customer-360', templateUrl: './asm-customer-360.component.html', - styles: [ - ` - ::ng-deep ngb-modal-window { - padding: 20px 10%; - } - - ::ng-deep .modal-dialog { - display: flex; - width: 100%; - max-height: 100%; - min-height: unset; - min-width: unset !important; - max-width: unset !important; - margin: unset; - } - - ::ng-deep .modal-body { - padding-top: 0; - } - `, - ], }) export class AsmCustomer360Component implements OnInit, OnDestroy { iconTypes = ICON_TYPE; diff --git a/feature-libs/asm/components/customer-emulation/customer-emulation.component.ts b/feature-libs/asm/components/customer-emulation/customer-emulation.component.ts index e4014daedec..bc2c12ef59e 100644 --- a/feature-libs/asm/components/customer-emulation/customer-emulation.component.ts +++ b/feature-libs/asm/components/customer-emulation/customer-emulation.component.ts @@ -44,6 +44,9 @@ export class CustomerEmulationComponent implements OnInit, OnDestroy { /* this.modalRef = this.modalService?.open(AsmCustomer360Component, { size: 'xl', + windowClass: 'asm-customer-360', + ariaLabelledBy: 'asm-customer-360-title', + ariaDescribedBy: 'asm-customer-360-desc', }); this.modalRef.componentInstance.customer = this.customer; */ diff --git a/projects/storefrontstyles/scss/cxbase/blocks/modal.scss b/projects/storefrontstyles/scss/cxbase/blocks/modal.scss index 982198a6dfd..462d7d5444d 100644 --- a/projects/storefrontstyles/scss/cxbase/blocks/modal.scss +++ b/projects/storefrontstyles/scss/cxbase/blocks/modal.scss @@ -50,7 +50,26 @@ $modal-min-width-asm: 95% !important; margin: auto; height: 100%; overflow-y: auto; +} + +.modal.asm-customer-360 { + padding: 20px 10%; + .modal-dialog { + display: flex; + width: 100%; + max-height: 100%; + min-height: unset; + min-width: unset !important; + max-width: unset !important; + margin: unset; + .modal-body { + padding-top: 0; + } + } +} +.modal-xl, +.modal-lg { max-width: $modal-max-width; min-width: $modal-min-width; From 2b815da287b696a64b425cfdf4e001139984d5db Mon Sep 17 00:00:00 2001 From: Hakwoo Kim Date: Sat, 6 Aug 2022 23:44:50 -0400 Subject: [PATCH 015/175] add missing module --- .../asm-customer-profile/asm-customer-profile.module.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.module.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.module.ts index 04b91f0ddab..e94505aa508 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.module.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.module.ts @@ -1,10 +1,11 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { I18nModule } from '@spartacus/core'; +import { CardModule } from '@spartacus/storefront'; import { AsmCustomerProfileComponent } from './asm-customer-profile.component'; @NgModule({ - imports: [CommonModule, I18nModule], + imports: [CardModule, CommonModule, I18nModule], declarations: [AsmCustomerProfileComponent], exports: [AsmCustomerProfileComponent], }) From 6050f9a98862c1bc421162143a252c75135f08f9 Mon Sep 17 00:00:00 2001 From: Jeremy Laverdiere Date: Wed, 10 Aug 2022 14:13:27 -0400 Subject: [PATCH 016/175] Fix search functionality for customer coupons --- .../asm-customer-promotions/asm-customer-promotions.model.ts | 4 ++-- .../asm-customer-listing/asm-customer-listing.module.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.model.ts index a5ee6d1ad03..688666ccb84 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.model.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.model.ts @@ -126,7 +126,7 @@ export class CustomerCouponFragment .pipe( tap(() => { item.applied = !item.applied; - this.tabs = this.tabulate(this.items); + this.searchAction(this.searchTerm); }) ) .subscribe(); @@ -135,7 +135,7 @@ export class CustomerCouponFragment searchAction(term: string): void { if (term.length) { const filteredItems = this.items.filter((item) => - item.description?.toLowerCase().includes(term.toLowerCase()) + item.description?.toLowerCase().includes(term.toLowerCase()) || item.title?.toLowerCase().includes(term.toLocaleLowerCase()) ); this.tabs = this.tabulate(filteredItems); } else { diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.module.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.module.ts index cbf3d6fbe98..2bbdff9b667 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.module.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.module.ts @@ -1,10 +1,11 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; import { IconModule } from '@spartacus/storefront'; import { AsmCustomerListingComponent } from './asm-customer-listing.component'; @NgModule({ - imports: [CommonModule, IconModule], + imports: [CommonModule, FormsModule, IconModule], declarations: [AsmCustomerListingComponent], exports: [AsmCustomerListingComponent], }) From ec443c269ddf06d326a39d60472eaf77e9ea07d4 Mon Sep 17 00:00:00 2001 From: justinlee01 <47992455+justinlee01@users.noreply.github.com> Date: Thu, 11 Aug 2022 08:45:01 -0400 Subject: [PATCH 017/175] Task/update asm 360 (#16064) * add navigation to customer 360 dialog * remove unnecessary styling * Asm360Overview route navigations close the modal properly. Lint fix using prettier:fix Co-authored-by: Paige Barron --- .../asm/assets/translations/en/asm.ts | 2 +- .../asm-customer-360.component.html | 23 +++++-- .../asm-customer-360.component.module.ts | 8 +-- .../asm-customer-360.component.ts | 27 +++++--- .../asm-customer-360.utils.ts | 2 +- .../asm-customer-activity.component.ts | 4 +- .../asm-customer-feedback.component.html | 5 +- .../asm-customer-feedback.model.ts | 7 +- .../asm-customer-overview.component.html | 7 +- .../asm-customer-overview.component.ts | 19 +++--- .../asm-customer-overview.model.ts | 2 +- .../asm-product-item.component.html | 20 +++--- .../asm-product-item.component.ts | 9 ++- .../asm-customer-profile.component.ts | 7 +- .../asm-customer-profile.model.ts | 4 +- .../asm-customer-promotions.component.html | 5 +- .../asm-customer-promotions.component.ts | 18 +++-- .../asm-customer-listing.component.html | 58 +++++++++++++---- .../asm-customer-table.component.html | 55 ++++++++++------ .../asm-customer-table.component.ts | 4 +- .../asm-customer-table.model.ts | 4 +- .../asm-main-ui/asm-main-ui.component.ts | 10 +-- .../customer-emulation.component.ts | 12 +++- .../services/asm-component.service.ts | 38 ++++++++++- feature-libs/asm/core/public_api.ts | 1 + feature-libs/asm/core/utils/utils.ts | 18 +++++ feature-libs/asm/root/model/asm.models.ts | 12 +++- .../_asm-customer-360.component.scss | 65 ++++++++++--------- .../_asm-customer-listing.component.scss | 4 +- .../components/_asm-customer-profile.scss | 1 - .../_asm-customer-table.component.scss | 9 ++- 31 files changed, 312 insertions(+), 148 deletions(-) create mode 100644 feature-libs/asm/core/utils/utils.ts diff --git a/feature-libs/asm/assets/translations/en/asm.ts b/feature-libs/asm/assets/translations/en/asm.ts index c9f35149f89..2b00a32e583 100644 --- a/feature-libs/asm/assets/translations/en/asm.ts +++ b/feature-libs/asm/assets/translations/en/asm.ts @@ -75,7 +75,7 @@ export const asm = { deliveryAddress: 'Delivery Address', phone1: 'Phone1', phone2: 'Phone2', - paymentMethodHeader: 'Saved Payment Methods' + paymentMethodHeader: 'Saved Payment Methods', }, customerOverview: { activeCart: 'Active Cart', diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html index 7f3f3c0a0dc..19f35765de1 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html @@ -38,11 +38,24 @@
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.ts index 057100688bf..a8af51f556b 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { AsmConfig } from '@spartacus/asm/core'; import { Cart } from '@spartacus/cart/base/root'; @@ -6,7 +6,7 @@ import { ImageType, PriceType, Product, - RoutingService, + UrlCommand, User, } from '@spartacus/core'; @@ -20,6 +20,9 @@ import { AsmInterestEntry } from './asm-customer-overview.model'; export class AsmCustomerOverviewComponent implements OnInit { @Input() customer: User; + @Output() + navigate: EventEmitter = new EventEmitter(); + activeCart$: Observable; savedCart$: Observable; @@ -28,10 +31,7 @@ export class AsmCustomerOverviewComponent implements OnInit { protected subscription = new Subscription(); - constructor( - protected asmConfig: AsmConfig, - protected routingService: RoutingService - ) {} + constructor(protected asmConfig: AsmConfig) {} ngOnInit(): void { if (this.customer?.uid) { @@ -42,17 +42,14 @@ export class AsmCustomerOverviewComponent implements OnInit { } onSelectProduct(selectedProduct: Product): void { - this.routingService.go({ cxRoute: 'product', params: selectedProduct }); - // TODO: we need to close main dialog + this.navigate.emit({ cxRoute: 'product', params: selectedProduct }); } onSelectCart(): void { // TODO emulate selected user and navigate to cart... } goToMyInterests(): void { - // TODO emulate selected user and navigate to Interests... - this.routingService.go({ cxRoute: 'myInterests' }); - + this.navigate.emit({ cxRoute: 'myInterests' }); } private getMockInterestData(): AsmInterestEntry { diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.model.ts index ceb67733d31..82404a11ac2 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.model.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.model.ts @@ -1,4 +1,4 @@ -import { Product } from "@spartacus/core"; +import { Product } from '@spartacus/core'; export interface AsmInterestEntry { products: Array; diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.html index f782ae241cf..7495fcdb5b2 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.html +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.html @@ -11,16 +11,20 @@ >
- {{ - product?.name - }} + {{ product?.name }}
{{ product?.code }}
{{ price?.formattedValue }}
-
+
{{ 'asm.customerOverview.outOfStock' | cxTranslate }}
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.ts index dd7f5f66b69..1ca42262443 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.ts @@ -1,4 +1,10 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, +} from '@angular/core'; import { OrderEntry } from '@spartacus/cart/base/root'; import { Price, Product } from '@spartacus/core'; @@ -15,5 +21,4 @@ export class AsmProductItemComponent { @Output() selectProduct = new EventEmitter(); constructor() {} - } diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.ts index 2bdb11358be..44ab07e0651 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.ts @@ -1,7 +1,12 @@ import { Component, Input, OnInit } from '@angular/core'; import { AsmConfig } from '@spartacus/asm/core'; -import { Address, PaymentDetails, TranslationService, User } from '@spartacus/core'; +import { + Address, + PaymentDetails, + TranslationService, + User, +} from '@spartacus/core'; import { BREAKPOINT, BreakpointService, diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.model.ts index 075abe2685f..1a9775ab27f 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.model.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.model.ts @@ -1,4 +1,4 @@ -import { Address, PaymentDetails } from "@spartacus/core"; +import { Address, PaymentDetails } from '@spartacus/core'; export interface CustomerProfileData { billingAddress?: Address; @@ -6,4 +6,4 @@ export interface CustomerProfileData { phone1?: String; phone2?: String; paymentInfoList?: PaymentDetails[]; -}; +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.html index 2a394ba179a..0534d6163b1 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.html +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.html @@ -1 +1,4 @@ - + diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.ts index 8c5338200ef..7bd4e0dbfe6 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.ts @@ -1,5 +1,9 @@ -import { Component } from "@angular/core"; -import { CouponFragment, PromotionFragment, CustomerCouponFragment } from "./asm-customer-promotions.model"; +import { Component } from '@angular/core'; +import { + CouponFragment, + PromotionFragment, + CustomerCouponFragment, +} from './asm-customer-promotions.model'; @Component({ selector: 'cx-asm-customer-promotions', @@ -27,12 +31,14 @@ export class AsmCustomerPromotionsComponent { items: [ { name: 'This is a 20% off coupon', - firedMessage: 'This is a 20% off coupon and you are gonna be so happy with these savings', + firedMessage: + 'This is a 20% off coupon and you are gonna be so happy with these savings', fired: true, }, { name: 'This is a 1% off coupon', - firedMessage: "This is a 1% off coupon and you won't really notice I'm here", + firedMessage: + "This is a 1% off coupon and you won't really notice I'm here", fired: false, }, ], @@ -63,12 +69,12 @@ export class AsmCustomerPromotionsComponent { }, { code: 'This is a 100% off coupon', - name: 'It\'s free, what else do you want?', + name: "It's free, what else do you want?", couponApplied: false, }, { code: 'This is a 66.666666% off coupon', - name: "This is the devil\'s discount. By accepting this, you accept Satan as your lord and savior :)", + name: "This is the devil's discount. By accepting this, you accept Satan as your lord and savior :)", couponApplied: true, }, ], diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.html index 770a486c9eb..f867f3aeb13 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.html +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.html @@ -3,48 +3,69 @@

- + - + " + >
-
+
{{ tab.tabName }}
- + " + > - + -
- + + @@ -56,10 +77,16 @@
{{ item.description }}
- -
+
{{ item.appliedText }} @@ -68,8 +95,11 @@
|
- {{ item.buttonText - }} + {{ item.buttonText }}
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.html index 06c68033978..ef99aa8c9b9 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.html +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.html @@ -3,9 +3,15 @@ {{ fragment.text }}
- -
+ +
{{ pageNumber + 1 }}
@@ -15,38 +21,45 @@ - - +
+ " + > {{ column.text }}
- {{ - entry[column.property].text || - entry[column.property].value || - '---' - }} + {{ + entry[column.property].text || entry[column.property].value || '---' + }} - + {{ - entry[column.property].text || - entry[column.property].value || - '---' - }} + entry[column.property].text || entry[column.property].value || '---' + }}
-
{{fragment.emptyText}}
+
+ {{ fragment.emptyText }} +
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.ts index 20503fe9bc1..ddb56be4a1a 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.ts @@ -1,5 +1,5 @@ -import { Component, Input } from "@angular/core"; -import { TableFragment } from "./asm-customer-table.model"; +import { Component, Input } from '@angular/core'; +import { TableFragment } from './asm-customer-table.model'; @Component({ selector: 'cx-asm-customer-table', diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.model.ts index f219a5a9a6c..7f884076bdd 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.model.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.model.ts @@ -1,6 +1,5 @@ - import { Fragment, Class } from '../../asm-customer-360.model'; -import { keyValuePair } from "../../asm-customer-360.model"; +import { keyValuePair } from '../../asm-customer-360.model'; export abstract class TableFragment implements Fragment { entries: Array; @@ -124,4 +123,3 @@ export interface TableEntryCell { reverseSort?: boolean; }; } - diff --git a/feature-libs/asm/components/asm-main-ui/asm-main-ui.component.ts b/feature-libs/asm/components/asm-main-ui/asm-main-ui.component.ts index 5cb78bd87ac..7b5c9722f57 100644 --- a/feature-libs/asm/components/asm-main-ui/asm-main-ui.component.ts +++ b/feature-libs/asm/components/asm-main-ui/asm-main-ui.component.ts @@ -107,7 +107,6 @@ export class AsmMainUiComponent implements OnInit, OnDestroy { } }) ); - this.csAgentTokenLoading$ = this.csAgentAuthService.getCustomerSupportAgentTokenLoading(); this.customer$ = this.authService.isUserLoggedIn().pipe( @@ -172,13 +171,8 @@ export class AsmMainUiComponent implements OnInit, OnDestroy { startCustomerEmulationSession({ customerId }: { customerId?: string }): void { if (customerId) { - this.csAgentAuthService.startCustomerEmulationSession(customerId); - this.startingCustomerSession = true; - } else { - this.globalMessageService.add( - { key: 'asm.error.noCustomerId' }, - GlobalMessageType.MSG_TYPE_ERROR - ); + this.startingCustomerSession = + this.asmComponentService.startCustomerEmulationSession(customerId); } } diff --git a/feature-libs/asm/components/customer-emulation/customer-emulation.component.ts b/feature-libs/asm/components/customer-emulation/customer-emulation.component.ts index bc2c12ef59e..6e9b17aed28 100644 --- a/feature-libs/asm/components/customer-emulation/customer-emulation.component.ts +++ b/feature-libs/asm/components/customer-emulation/customer-emulation.component.ts @@ -5,6 +5,7 @@ */ import { Component, OnDestroy, OnInit } from '@angular/core'; +// import { AsmDialogActionEvent } from '@spartacus/asm/root'; import { User } from '@spartacus/core'; import { UserAccountFacade } from '@spartacus/user/account/root'; import { Observable, Subscription } from 'rxjs'; @@ -49,7 +50,16 @@ export class CustomerEmulationComponent implements OnInit, OnDestroy { ariaDescribedBy: 'asm-customer-360-desc', }); this.modalRef.componentInstance.customer = this.customer; - */ + this.modalRef?.result + .then((event: AsmDialogActionEvent) => { + this.asmComponentService.handleAsmDialogAction(event); + this.modalRef = undefined; + }) + .catch(() => { + // this callback is called when modal is closed with Esc key or clicking backdrop + this.modalRef = undefined; + }); + */ } ngOnDestroy(): void { diff --git a/feature-libs/asm/components/services/asm-component.service.ts b/feature-libs/asm/components/services/asm-component.service.ts index 8f939a7f997..5fe4000e7a4 100644 --- a/feature-libs/asm/components/services/asm-component.service.ts +++ b/feature-libs/asm/components/services/asm-component.service.ts @@ -6,10 +6,17 @@ import { Injectable } from '@angular/core'; import { + AsmDialogActionEvent, ASM_ENABLED_LOCAL_STORAGE_KEY, CsAgentAuthService, } from '@spartacus/asm/root'; -import { AuthService, WindowRef } from '@spartacus/core'; +import { + AuthService, + GlobalMessageService, + GlobalMessageType, + RoutingService, + WindowRef, +} from '@spartacus/core'; import { Observable } from 'rxjs'; @Injectable({ @@ -19,6 +26,8 @@ export class AsmComponentService { constructor( protected authService: AuthService, protected csAgentAuthService: CsAgentAuthService, + protected globalMessageService: GlobalMessageService, + protected routingService: RoutingService, protected winRef: WindowRef ) {} @@ -30,10 +39,37 @@ export class AsmComponentService { this.authService.logout(); } + startCustomerEmulationSession(customerId: string): boolean { + if (customerId) { + this.csAgentAuthService.startCustomerEmulationSession(customerId); + return true; + } else { + this.globalMessageService.add( + { key: 'asm.error.noCustomerId' }, + GlobalMessageType.MSG_TYPE_ERROR + ); + return false; + } + } + isCustomerEmulationSessionInProgress(): Observable { return this.csAgentAuthService.isCustomerEmulated(); } + // TODO: Find better place for method + handleAsmDialogAction(event: AsmDialogActionEvent): void { + let selectedUser = event.selectedUser?.customerId; + let { cxRoute, params } = event.route; + if (event.actionType === 'START_SESSION' && selectedUser) { + this.startCustomerEmulationSession(selectedUser); + if (cxRoute) { + this.routingService.go({ cxRoute, params }); + } + } else { + this.routingService.go({ cxRoute, params }); + } + } + /** * We're currently only removing the persisted storage in the browser * to ensure the ASM experience isn't loaded on the next visit. There are a few diff --git a/feature-libs/asm/core/public_api.ts b/feature-libs/asm/core/public_api.ts index cda99ab4709..fe534c01670 100644 --- a/feature-libs/asm/core/public_api.ts +++ b/feature-libs/asm/core/public_api.ts @@ -13,3 +13,4 @@ export * from './services/index'; export * from './store/actions/index'; export * from './store/asm-state'; export * from './store/selectors/index'; +export * from './utils/utils'; diff --git a/feature-libs/asm/core/utils/utils.ts b/feature-libs/asm/core/utils/utils.ts new file mode 100644 index 00000000000..56493a0e602 --- /dev/null +++ b/feature-libs/asm/core/utils/utils.ts @@ -0,0 +1,18 @@ +import { AsmDialogActionEvent, AsmDialogActionType } from '@spartacus/asm/root'; +import { UrlCommand, User } from '@spartacus/core'; + +/** + * Return event from ASM dialog action + */ +export function getAsmDialogActionEvent( + customerEntry: User, + action: AsmDialogActionType, + route?: UrlCommand +): AsmDialogActionEvent { + let event: AsmDialogActionEvent = { + actionType: action, + selectedUser: customerEntry, + route: route, + }; + return event; +} diff --git a/feature-libs/asm/root/model/asm.models.ts b/feature-libs/asm/root/model/asm.models.ts index a7c3255cff9..f007cd7a855 100644 --- a/feature-libs/asm/root/model/asm.models.ts +++ b/feature-libs/asm/root/model/asm.models.ts @@ -1,5 +1,9 @@ -import { PaginationModel, SortModel, User } from '@spartacus/core'; +import { PaginationModel, SortModel, UrlCommand, User } from '@spartacus/core'; +export enum AsmDialogActionType { + START_SESSION = 'START_SESSION', + NAVIGATE = 'NAVIGATE', +} export interface CustomerSearchPage { entries: User[]; pagination?: PaginationModel; @@ -25,6 +29,12 @@ export interface Customer360Section { sectionContent: string; } +export interface AsmDialogActionEvent { + selectedUser: User; + actionType: AsmDialogActionType; + route?: UrlCommand; +} + export enum CUSTOMER_360_SECTION_TITLE { OVERVIEW = 'OVERVIEW', PROFILE = 'PROFILE', diff --git a/feature-libs/asm/styles/components/_asm-customer-360.component.scss b/feature-libs/asm/styles/components/_asm-customer-360.component.scss index 522e3d67375..41a4b4104dc 100644 --- a/feature-libs/asm/styles/components/_asm-customer-360.component.scss +++ b/feature-libs/asm/styles/components/_asm-customer-360.component.scss @@ -11,7 +11,6 @@ flex: none; padding-top: 2rem; padding-bottom: 3rem; - display: flex; .cx-avatar { font-size: 56px; width: 110px; @@ -50,7 +49,7 @@ flex: 1; flex-direction: column; padding-top: 0; - overflow: auto; + overflow-y: hidden; .cx-dialog-row { flex: none; } @@ -59,38 +58,46 @@ border-bottom: 1px solid #dddddd; margin-top: 20px; padding-bottom: 8px; - .cx-tab { - padding: 10px 20px; - color: var(--cx-color-light); - border-bottom: 1px solid #dddddd; - margin-right: 1px; - cursor: pointer; - &.active { - cursor: default; - } - &.active, - &:hover, - &:focus { - border-width: 0 0 2px; - padding: 10px 20px 9px 20px; - color: var(--cx-color-visual-focus); - text-decoration: none; - } - &:hover { - border-color: #556b82; - } + } + .cx-tab { + padding: 10px 20px; + color: var(--cx-color-light); + border-bottom: 1px solid #dddddd; + cursor: pointer; + &.active { + cursor: default; + } + &:hover { + border-color: #556b82; + color: var(--cx-color-visual-focus); + border-width: 0 0 2px; + text-decoration: none; } - .active { + &:active, + &:hover, + &:focus { + color: var(--cx-color-visual-focus); + border-width: 0 0 2px; + text-decoration: none; + border-color: var(--cx-color-visual-focus); + padding: 10px 20px 9px 20px; + } + } + .cx-tab-content { + flex: 1; + min-height: 0; + overflow-y: auto; + } + .active { + color: var(--cx-color-visual-focus); + border-width: 0 0 2px; + text-decoration: none; + border-color: var(--cx-color-visual-focus); + &:hover { color: var(--cx-color-visual-focus); border-width: 0 0 2px; text-decoration: none; border-color: var(--cx-color-visual-focus); - &:hover { - color: var(--cx-color-visual-focus); - border-width: 0 0 2px; - text-decoration: none; - border-color: var(--cx-color-visual-focus); - } } } } diff --git a/feature-libs/asm/styles/components/_asm-customer-listing.component.scss b/feature-libs/asm/styles/components/_asm-customer-listing.component.scss index e8773d5428b..ac159bcac4e 100644 --- a/feature-libs/asm/styles/components/_asm-customer-listing.component.scss +++ b/feature-libs/asm/styles/components/_asm-customer-listing.component.scss @@ -111,7 +111,9 @@ margin-right: 1px; margin-bottom: -1px; - &.active, &:hover, &:focus { + &.active, + &:hover, + &:focus { color: #0486e0; border-bottom: solid; border-width: 2px; diff --git a/feature-libs/asm/styles/components/_asm-customer-profile.scss b/feature-libs/asm/styles/components/_asm-customer-profile.scss index 7067f112fd7..218b89b278e 100644 --- a/feature-libs/asm/styles/components/_asm-customer-profile.scss +++ b/feature-libs/asm/styles/components/_asm-customer-profile.scss @@ -13,7 +13,6 @@ } } - .cx-asm-profile-address-cell { display: flex; flex-direction: column; diff --git a/feature-libs/asm/styles/components/_asm-customer-table.component.scss b/feature-libs/asm/styles/components/_asm-customer-table.component.scss index 745c91b7ffe..0332e13c8b4 100644 --- a/feature-libs/asm/styles/components/_asm-customer-table.component.scss +++ b/feature-libs/asm/styles/components/_asm-customer-table.component.scss @@ -60,7 +60,8 @@ } } - th, td { + th, + td { text-align: left; vertical-align: top; padding: 10px; @@ -86,12 +87,14 @@ cursor: pointer; user-select: none; - &:hover, &.active { + &:hover, + &.active { color: #454a5d; } &.active { - &.desc::after, &.asc::after { + &.desc::after, + &.asc::after { content: ''; margin-left: 8px; border-left: 5px solid transparent; From 9b70e856769b2c77d737bfa01d5bebaa96faa840 Mon Sep 17 00:00:00 2001 From: justinlee01 <47992455+justinlee01@users.noreply.github.com> Date: Thu, 25 Aug 2022 13:29:03 -0400 Subject: [PATCH 018/175] Task: customer 360 updates (#16071) * Adding ASM Customer 360 configs for page size; not used yet however * Storing Google Maps API key in ASM configuration instead of hardcoded in component: * Updating Customer Overview SCSS and some of the tab SCSS --- .../asm-customer-map.component.html | 1 + .../asm-customer-map.component.scss | 1 + .../asm-customer-map.component.ts | 9 +- .../asm-customer-overview.component.html | 121 ++++++++++-------- .../asm-customer-promotions.model.ts | 6 +- feature-libs/asm/root/config/asm-config.ts | 19 +++ .../asm/root/config/default-asm-config.ts | 19 +++ .../_asm-customer-360.component.scss | 11 +- .../_asm-customer-listing.component.scss | 3 + .../components/_asm-customer-overview.scss | 65 +++++++++- .../components/_asm-customer-profile.scss | 3 + .../_asm-customer-table.component.scss | 3 + 12 files changed, 195 insertions(+), 66 deletions(-) diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.html index ce094285ac3..51858176fba 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.html +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.html @@ -36,6 +36,7 @@
-
-
-
- {{ opening.key }}{{ opening.value }} -
-
-
-
{{ feature }}
-
-
-
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.ts deleted file mode 100644 index 106423f938d..00000000000 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { HttpParams } from '@angular/common/http'; -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - OnInit, -} from '@angular/core'; -import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; -import { AsmConfig } from '@spartacus/asm/core'; - -import { MapData, StoreData } from './map-types'; - -@Component({ - changeDetection: ChangeDetectionStrategy.OnPush, - selector: 'cx-asm-customer-map', - templateUrl: './asm-customer-map.component.html', - styleUrls: ['./asm-customer-map.component.scss'], -}) -export class AsmCustomerMapComponent implements OnInit { - storeData: MapData = { - total: 3, - data: [ - { - id: '1', - name: 'Boston', - displayName: 'Boston', - line1: '53 State St, Boston, MA 02109', - line2: 'Floor 16', - town: 'Boston', - formattedDistance: '2 miles', - latitude: 42.358856201171875, - longitude: -71.05696868896484, - image: - 'https://s36700.pcdn.co/wp-content/uploads/2015/05/dachshund-puppies-03.jpg.optimal.jpg', - productcode: 'productcode 1', - openings: { - 'Mon - Sat': '09:00 - 20:00', - Sun: '09:00 - 17:00', - }, - features: ['Wheelchair accessible'], - }, - { - id: '2', - name: 'Burlington', - displayName: 'Burlington', - line1: '15 Wayside Rd, Burlington, MA 01803', - line2: '', - town: 'Burlington', - formattedDistance: '5 miles', - latitude: 42.485267639160156, - longitude: -71.19204711914062, - image: - 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTWApY7vpCoiyrYKL1FUsfNDwYUSNPTG5TZlQ&usqp=CAU', - productcode: 'productcode 2', - openings: { - 'Mon - Sat': '09:00 - 20:00', - Sun: '09:00 - 17:00', - }, - features: ['Wheelchair accessible'], - }, - { - id: '3', - name: 'Palo Alto', - displayName: 'Palo Alto', - line1: '3410 Hillview Avenue, Palo Alto, CA 94304, USA', - line2: '', - town: 'Palo Alto', - formattedDistance: '200 miles', - latitude: 37.4443293, - longitude: -122.1598465, - image: - 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/golden-retreiver-puppy-appears-on-nbc-news-today-show-on-news-photo-164288748-1551895571.jpg?crop=0.85948xw:1xh;center,top&resize=480:*', - productcode: 'productcode 3', - openings: { - 'Mon - Sat': '09:00 - 20:00', - Sun: '09:00 - 17:00', - }, - features: ['Wheelchair accessible', 'Taco Tuesday'], - }, - ], - }; - - readonly apiKey: string | undefined; - - currentLocation: string; - - googleMapsUrl: SafeResourceUrl; - - selectedStore: StoreData; - - constructor( - asmConfig: AsmConfig, - protected changeDetectorRef: ChangeDetectorRef, - protected sanitizer: DomSanitizer - ) { - this.apiKey = asmConfig.asm?.customer360?.mapsTab?.googleMaps?.apiKey; - this.selectedStore = this.storeData.data[0]; - } - - ngOnInit(): void { - navigator.geolocation.getCurrentPosition((position) => { - this.currentLocation = `${position.coords.latitude},${position.coords.longitude}`; - - this.updateGoogleMapsUrl(); - - this.changeDetectorRef.detectChanges(); - }); - } - - updateGoogleMapsUrl(): void { - if (this.apiKey && this.currentLocation) { - const coordinates = `${this.selectedStore.latitude},${this.selectedStore.longitude}`; - - const params = new HttpParams() - .append('key', this.apiKey) - .append('origin', this.currentLocation) - .append('destination', coordinates); - - this.googleMapsUrl = this.sanitizer.bypassSecurityTrustResourceUrl( - `https://www.google.com/maps/embed/v1/directions?${params.toString()}` - ); - } - } - - selectStore(store: StoreData): void { - this.selectedStore = store; - - this.updateGoogleMapsUrl(); - } -} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.html deleted file mode 100644 index 0534d6163b1..00000000000 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.html +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.ts deleted file mode 100644 index 7bd4e0dbfe6..00000000000 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.component.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Component } from '@angular/core'; -import { - CouponFragment, - PromotionFragment, - CustomerCouponFragment, -} from './asm-customer-promotions.model'; - -@Component({ - selector: 'cx-asm-customer-promotions', - templateUrl: './asm-customer-promotions.component.html', -}) -export class AsmCustomerPromotionsComponent { - rawCouponFragment = { - type: 'coupons', - items: [ - { - code: 'This is a 20% off coupon', - name: 'This is a 20% off coupon and you are gonna be so happy with these savings', - couponApplied: false, - }, - { - code: 'This is a 1% off coupon', - name: "This is a 1% off coupon and you won't really notice I'm here", - couponApplied: true, - }, - ], - }; - - rawPromotionFragment = { - type: 'promotions', - items: [ - { - name: 'This is a 20% off coupon', - firedMessage: - 'This is a 20% off coupon and you are gonna be so happy with these savings', - fired: true, - }, - { - name: 'This is a 1% off coupon', - firedMessage: - "This is a 1% off coupon and you won't really notice I'm here", - fired: false, - }, - ], - }; - - rawCustomerCouponFragment = { - type: 'customerCoupons', - items: [ - { - code: 'This is a 20% off coupon', - name: 'This is a 20% off coupon and you are gonna be so happy with these savings', - couponApplied: true, - }, - { - code: 'This is a 1% off coupon', - name: "This is a 1% off coupon and you won't really notice I'm here", - couponApplied: false, - }, - { - code: 'This is a 500% off coupon', - name: 'This is a 500% off coupon and you are gonna be so happy with these earnings', - couponApplied: false, - }, - { - code: 'This is a 0% off coupon', - name: "This is a 0% off coupon and you literally won't notice I'm here", - couponApplied: true, - }, - { - code: 'This is a 100% off coupon', - name: "It's free, what else do you want?", - couponApplied: false, - }, - { - code: 'This is a 66.666666% off coupon', - name: "This is the devil's discount. By accepting this, you accept Satan as your lord and savior :)", - couponApplied: true, - }, - ], - }; - - uiFragments = [ - new CouponFragment(this.rawCouponFragment.items), - new PromotionFragment(this.rawPromotionFragment.items), - new CustomerCouponFragment(this.rawCustomerCouponFragment.items), - ]; -} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.model.ts deleted file mode 100644 index d74d109b4ac..00000000000 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.model.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { of } from 'rxjs'; -import { tap } from 'rxjs/operators'; -import { - ListingFragment, - ListingItem, - RawListingItem, - Searchable, - Tabulated, -} from '../asm-customer-ui-components/asm-customer-listing/asm-customer-listing.model'; - -export interface RawPromotionItem extends RawListingItem { - code?: string; - name?: string; - couponApplied?: boolean; - - description?: string; - firedMessage?: string; - fired?: boolean; - priority?: number; -} - -export class CouponListingItem extends ListingItem { - title?: string; - description?: string; - applied?: boolean; - textButton?: boolean; - - get buttonText(): string { - return this.applied ? 'REMOVE' : 'APPLY TO CART'; - } - - get appliedText(): string | undefined { - return this.applied ? 'COUPON APPLIED' : undefined; - } - - constructor(item: RawPromotionItem) { - super(); - this.title = item.code; - this.description = item.name; - this.applied = item.couponApplied; - this.textButton = item.couponApplied; - } -} - -export class PromotionListingItem extends ListingItem { - title?: string; - description?: string; - applied?: boolean; - - get appliedText(): string | undefined { - return this.applied ? 'PROMOTION APPLIED' : undefined; - } - - constructor(item: RawPromotionItem) { - super(); - this.title = item.name; - this.description = item.firedMessage; - this.applied = item.fired; - } -} - -export class CustomerCouponListingItem extends ListingItem { - title?: string; - description?: string; - applied?: boolean; - - get buttonText(): string { - return this.applied ? 'REMOVE' : 'SEND TO CUSTOMER'; - } - - constructor(item: RawPromotionItem) { - super(); - this.title = item.code; - this.description = item.name; - this.applied = item.couponApplied; - } -} - -export class CouponFragment extends ListingFragment { - type = 'coupons'; - text = 'COUPONS'; - emptyText = 'There are currently no CSA Coupons available.'; - items: Array; - - buttonAction(item: CouponListingItem) { - of(undefined) - .pipe( - tap(() => { - item.applied = !item.applied; - item.textButton = item.applied; - }) - ) - .subscribe(); - } - - constructor(rawItems: Array) { - super(rawItems, CouponListingItem); - } -} - -export class PromotionFragment extends ListingFragment { - type = 'promotions'; - text = 'PROMOTIONS'; - emptyText = 'There are currently no CSA Promotions available.'; - items: Array; - - constructor(rawItems: Array) { - super(rawItems, PromotionListingItem); - } -} - -export class CustomerCouponFragment - extends ListingFragment - implements Tabulated, Searchable -{ - type = 'customerCoupons'; - text = 'CUSTOMER COUPONS'; - items: Array; - searchable: Searchable; - tabs: Tabulated['tabs']; - currentTab = 0; - searchTerm = ''; - - buttonAction(item: CustomerCouponListingItem) { - of(undefined) - .pipe( - tap(() => { - item.applied = !item.applied; - this.searchAction(this.searchTerm); - }) - ) - .subscribe(); - } - - searchAction(term: string): void { - if (term.length) { - const filteredItems = this.items.filter( - (item) => - item.description?.toLowerCase().includes(term.toLowerCase()) || - item.title?.toLowerCase().includes(term.toLocaleLowerCase()) - ); - this.tabs = this.tabulate(filteredItems); - } else { - this.tabs = this.tabulate(this.items); - } - } - - setCurrentTab(tab: number): void { - this.currentTab = tab; - } - - private tabulate(items: Array): Tabulated['tabs'] { - const tabs: Tabulated['tabs'] = [ - { ...this.tabs[0], items: [] }, - { ...this.tabs[1], items: [] }, - ]; - - items.forEach((item) => { - const index = Number(item.applied); - tabs[index].items.push(item); - }); - - return tabs; - } - - constructor(rawItems: Array) { - super(rawItems, CustomerCouponListingItem); - this.tabs = [ - { - tabName: 'AVAILABLE', - items: [], - emptyText: 'There are currently no available customer coupons.', - }, - { - tabName: 'SENT', - items: [], - emptyText: 'There are currently no sent customer coupons.', - }, - ]; - this.tabs = this.tabulate(this.items); - } -} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.html index f867f3aeb13..d0e97fba2e7 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.html +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.html @@ -1,45 +1,44 @@ -
- {{ fragment.text }} +
+ {{ headerText }}

- - - + + - +
- +
{{ tab.tabName }}
- - + + - - - - - - +
+ + +
+
{{ item.title }}
{{ item.description }}
-
- -
- -
- {{ item.appliedText }} -
- -
- | +
+ + + + + + + +
+ +
+ {{ buttonConfig.appliedText }}
- {{ item.buttonText }} - -
+ +
|
+ {{ buttonConfig.removeText }} +
+
+
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.ts index 7dfc44a3c7f..422138b4ca0 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.component.ts @@ -1,10 +1,50 @@ -import { Component, Input } from '@angular/core'; -import { ListingFragmentType } from './asm-customer-listing.model'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, +} from '@angular/core'; + +import { + CustomerListingButton, + CustomerListingTab, + ListingItem, +} from './asm-customer-listing.model'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, selector: 'cx-asm-customer-listing', templateUrl: './asm-customer-listing.component.html', }) export class AsmCustomerListingComponent { - @Input() fragment: ListingFragmentType; + @Input() + buttonConfig: CustomerListingButton; + + @Input() + currentTab: number; + + @Input() + emptyStateText: string; + + @Input() + headerText: string; + + @Input() + items: Array; + + /** If this is defined, 'items' will be ignored. */ + @Input() + tabs: Array; + + @Output() + actOn: EventEmitter = new EventEmitter(); + + @Output() + search: EventEmitter = new EventEmitter(); + + @Output() + clickTab: EventEmitter = new EventEmitter(); + + searchTerm: string; } diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.model.ts index f0c55dbfca1..d518ea00ee0 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.model.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-listing/asm-customer-listing.model.ts @@ -1,41 +1,19 @@ -import { Class, Fragment } from '../../asm-customer-360.model'; - -export abstract class ListingFragment implements Fragment { - abstract type: string; - abstract text: string; +export interface CustomerListingTab { + buttonConfig?: CustomerListingButton; + tabName: string; items: Array; - emptyText?: string; - buttonAction?(item: ListingItem): void; - - constructor( - rawItems: Array, - itemClass: Class - ) { - this.items = rawItems.map((item) => new itemClass(item)); - } -} - -export interface Searchable extends ListingFragment { - searchTerm: string; - searchAction(term: string): void; + emptyText: string; } -export interface Tabulated extends ListingFragment { - currentTab: number; - tabs: Array<{ - tabName: string; - items: Array; - emptyText: string; - }>; - setCurrentTab(tab: number): void; -} - -export type ListingFragmentType = ListingFragment & Searchable & Tabulated; - -export class ListingItem { - [key: string]: string | boolean | undefined; +export interface CustomerListingButton { + applyText: string; + appliedText?: string; + removeText?: string; + isApplied?: (item: ListingItem) => boolean; } -export interface RawListingItem { - [key: string]: string | boolean | number | undefined; +export interface ListingItem { + title: string | undefined; + description: string | undefined; + applied: boolean; } diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.html index ef99aa8c9b9..8f80fc877f3 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.html +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.html @@ -1,16 +1,14 @@ -
+
- {{ fragment.text }} + {{ headerText }}
-
- +
+
{{ pageNumber + 1 }}
@@ -18,48 +16,45 @@

- + - - +
{{ column.text }}
- {{ - entry[column.property].text || entry[column.property].value || '---' - }} +
+ + + + {{ + entry[column.property].text || '---' + }} + + + - {{ - entry[column.property].text || entry[column.property].value || '---' - }} + {{ entry[column.property] || '---' }}
-
- {{ fragment.emptyText }} +
+ {{ emptyStateText }}
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.ts index ddb56be4a1a..d897ff4f28e 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.component.ts @@ -1,10 +1,122 @@ -import { Component, Input } from '@angular/core'; -import { TableFragment } from './asm-customer-table.model'; +import { + ChangeDetectionStrategy, + Component, + Input, + OnInit, +} from '@angular/core'; + +import { KeyValuePair } from '../../asm-customer-360.model'; +import { CustomerTableColumn, TableEntry } from './asm-customer-table.model'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, selector: 'cx-asm-customer-table', templateUrl: './asm-customer-table.component.html', }) -export class AsmCustomerTableComponent { - @Input() fragment: TableFragment; +export class AsmCustomerTableComponent implements OnInit { + @Input() + columns: Array; + + @Input() + currentPage = 0; + + @Input() + emptyStateText: string; + + @Input() + entries: Array; + + @Input() + headerText: string; + + @Input() + pageSize: number; + + @Input() + sortProperty: string; + + reverseSort = false; + + entryPages: Array>; + + ngOnInit(): void { + const entries = this.sortEntries(this.entries, this.sortProperty); + this.entryPages = this.updateEntryPages(entries); + } + + public sortEntriesAndUpdatePages(property: string): void { + const currentProperty = this.sortProperty; + + let reverseSort: boolean; + + if (property !== currentProperty) { + reverseSort = false; + } else { + reverseSort = !this.reverseSort; + } + + this.sortProperty = property; + this.reverseSort = reverseSort; + + this.entries = this.sortEntries(this.entries, property, reverseSort); + this.entryPages = this.updateEntryPages(this.entries); + } + + public setPageNumber(pageNumber: number): void { + this.currentPage = pageNumber; + } + + private updateEntryPages( + entries: Array + ): Array> { + const newEntryPages = []; + for (let i = 0; i < entries.length; i += this.pageSize) { + newEntryPages.push(entries.slice(i, i + this.pageSize)); + } + return newEntryPages; + } + + private sortEntries( + entries: Array, + sortByProperty: string, + reverseSort?: boolean + ): Array { + const newEntries = entries.slice().sort((entryA, entryB) => { + const prev = entryA[sortByProperty]; + const next = entryB[sortByProperty]; + return this.compareEntryValues(prev, next); + }); + + if (reverseSort) { + return newEntries.reverse(); + } else { + return newEntries; + } + } + + private compareEntryValues( + a: string | number | Array | undefined, + b: string | number | Array | undefined + ): number { + if (a === undefined || b === undefined) { + if (a === undefined && b === undefined) { + return 0; + } else if (a === undefined) { + return -1; + } else { + return 1; + } + } else if (typeof a !== typeof b) { + // Assume the values need to be of the same type to be compared. + throw new TypeError('Compared values need to be of the same type.'); + } else if (Array.isArray(a) || Array.isArray(b)) { + // Ignore KeyValuePairs, assuming they will not be sorted. + return 0; + } else if (typeof a === 'string') { + return a.localeCompare(b as string); + } else { + // Assuming both values are 'number' now. + return a - (b as number); + } + } } diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.model.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.model.ts index 7f884076bdd..e98d6060c8b 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.model.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-ui-components/asm-customer-table/asm-customer-table.model.ts @@ -1,125 +1,14 @@ -import { Fragment, Class } from '../../asm-customer-360.model'; -import { keyValuePair } from '../../asm-customer-360.model'; +import { KeyValuePair } from '../../asm-customer-360.model'; -export abstract class TableFragment implements Fragment { - entries: Array; - columns: Array<{ - text: string; - property: string; - sortOrder?: 'asc' | 'desc'; - }>; - currentSort: { - property: string; - sortOrder: 'asc' | 'desc'; - }; - entryPages: Array>; - currentPage: number; - pageSize: number; - - abstract emptyText: string; - abstract type: string; - abstract text: string; - - public setPageNumber(pageNumber: number): void { - this.currentPage = pageNumber; - } - - public sortEntriesAndUpdatePages(sortByProperty: string): void { - this.entries = this.sortEntries(this.entries, sortByProperty); - this.entryPages = this.updateEntryPages(this.entries); - } - - private updateEntryPages( - entries: Array - ): Array> { - const newEntryPages = []; - for (let i = 0; i < entries.length; i += this.pageSize) { - newEntryPages.push(entries.slice(i, i + this.pageSize)); - } - return newEntryPages; - } - - private sortEntries( - entries: Array, - sortByProperty: string - ): Array { - if (sortByProperty === this.currentSort?.property) { - if (this.currentSort.sortOrder === 'asc') { - this.currentSort.sortOrder = 'desc'; - } else if (this.currentSort.sortOrder === 'desc') { - this.currentSort.sortOrder = 'asc'; - } - } else if (this.currentSort?.property !== sortByProperty) { - this.currentSort.property = sortByProperty; - this.currentSort.sortOrder = 'asc'; - } - const newEntries = entries.slice(); - return newEntries.sort((entryA, entryB) => { - const prev = entryA[sortByProperty]?.value; - const next = entryB[sortByProperty]?.value; - let dir = 1; - if (entryA[sortByProperty]?.details?.reverseSort) { - dir = -1; - } - if (this.currentSort.sortOrder === 'asc') { - if (prev !== undefined && next !== undefined) { - return prev < next ? dir * -1 : dir * 1; - } else { - return prev !== undefined ? -1 : 1; - } - } else { - if (prev !== undefined && next !== undefined) { - return prev < next ? dir * 1 : dir * -1; - } else { - return prev !== undefined ? -1 : 1; - } - } - }); - } - - constructor( - rawEntries: Array, - sortProperty: string, - entryClass: Class, - pageSize: number = 5, - currentPage: number = 0 - ) { - this.currentPage = currentPage; - this.pageSize = pageSize; - this.currentSort = { property: sortProperty, sortOrder: 'desc' }; - this.entries = this.sortEntries( - rawEntries.map((rawEntry) => new entryClass(rawEntry)), - this.currentSort.property - ); - this.entryPages = this.updateEntryPages(this.entries); - this.columns = Object.getOwnPropertyNames(new entryClass({})).map((key) => { - let column: { - property: string; - text: string; - sortOrder?: 'asc' | 'desc'; - } = { property: key, text: key.toUpperCase() }; - if (key === this.currentSort.property) { - column.sortOrder = this.currentSort.sortOrder; - } - return column; - }); - } -} - -export interface RawTableEntry { - [key: string]: string | Array | number | undefined; -} - -export class TableEntry { - [key: string]: TableEntryCell; +export interface CustomerTableColumn { + text: string; + property: string; + sortOrder?: 'asc' | 'desc'; + renderAsStarRating?: boolean; + /** If truthy, use the value to read the value from the entry to link to. */ + urlProperty?: string; } -export interface TableEntryCell { - value?: string | number; - text?: string; - details?: { - url?: string; - starRating?: boolean; - reverseSort?: boolean; - }; +export interface TableEntry { + [key: string]: string | Array | number | undefined; } diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.component.html b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.component.html new file mode 100644 index 00000000000..a268e320d8d --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.component.html @@ -0,0 +1,8 @@ + diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.component.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.component.ts new file mode 100644 index 00000000000..2457d55b955 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.component.ts @@ -0,0 +1,128 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { Customer360SectionConfig } from 'feature-libs/asm/core/models/customer-360-section-config'; + +import { formatEpochTime } from '../../asm-customer-360.utils'; +import { CustomerTableColumn } from '../../asm-customer-ui-components/asm-customer-table/asm-customer-table.model'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: 'cx-asm-customer-activity', + templateUrl: './asm-customer-activity.component.html', +}) +export class AsmCustomerActivityComponent { + columns: Array = [ + { property: 'type', text: 'type' }, + { property: 'id', text: 'id' }, + { property: 'description', text: 'description' }, + { property: 'category', text: 'status' }, + { property: 'created', text: 'created' }, + { property: 'updated', text: 'updated' }, + ]; + + entries = [ + { + type: 'Ticket', + id: '00000001', + description: 'Thing not work good', + created: Number(new Date('2022-07-07T18:25:43.511Z')), + updated: Number(new Date('2022-07-07T18:25:43.511Z')), + category: 'New', + }, + { + type: 'Ticket', + id: '00000002', + description: 'Thing not work as expected', + created: Number(new Date('2022-07-03T18:25:43.511Z')), + updated: Number(new Date('2022-07-06T18:25:43.511Z')), + category: 'Closed', + }, + { + type: 'Ticket', + id: '00000003', + description: 'Thing work but so slow ${cool}', + descriptionArgs: [{ key: 'cool', value: 'beans' }], + created: Number(new Date('2022-06-30T18:25:43.511Z')), + updated: Number(new Date('2022-07-01T18:25:43.511Z')), + category: 'Addressed', + }, + { + type: 'Cart', + id: '00002001', + description: 'Cart with 1 item', + created: Number(new Date('2022-07-01T18:25:43.511Z')), + updated: Number(new Date('2022-07-02T18:25:43.511Z')), + url: 'https://www.example.com/00002001', + }, + { + type: 'Cart', + id: '00002004', + description: 'Cart with 2 items', + created: Number(new Date('2022-06-01T18:25:43.511Z')), + updated: Number(new Date('2022-07-06T18:25:43.511Z')), + url: 'https://www.example.com/00002004', + }, + { + type: 'Cart', + id: '00002007', + description: 'Cart with 0 items', + created: Number(new Date('2022-06-15T18:25:43.511Z')), + updated: Number(new Date('2022-06-20T18:25:43.511Z')), + url: 'https://www.example.com/00002007', + }, + { + type: 'Saved Cart', + id: '00002002', + description: 'Cart with 2 items', + created: Number(new Date('2022-07-02T18:25:43.511Z')), + updated: Number(new Date('2022-07-04T18:25:43.511Z')), + }, + { + type: 'Saved Cart', + id: '00002005', + description: 'Cart with 3 items', + created: Number(new Date('2022-06-09T18:25:43.511Z')), + updated: Number(new Date('2022-06-12T18:25:43.511Z')), + }, + { + type: 'Saved Cart', + id: '00002008', + description: 'Cart with 4 items', + created: Number(new Date('2022-06-22T18:25:43.511Z')), + updated: Number(new Date('2022-06-22T18:25:43.511Z')), + }, + { + type: 'Order', + id: '00002003', + description: 'Cart with 1 item', + created: Number(new Date('2022-05-30T18:25:43.511Z')), + updated: Number(new Date('2022-05-31T18:25:43.511Z')), + category: 'Draft', + }, + { + type: 'Order', + id: '00002006', + description: 'Cart with 2 items', + created: Number(new Date('2022-06-05T18:25:43.511Z')), + updated: Number(new Date('2022-06-06T18:25:43.511Z')), + category: 'Completed', + }, + { + type: 'Order', + id: '00002009', + description: 'Cart with 0 items', + created: Number(new Date('2022-05-15T18:25:43.511Z')), + updated: Number(new Date('2022-05-20T18:25:43.511Z')), + category: 'Processing', + }, + ]; + + transformedEntries: Array; + + constructor(public config: Customer360SectionConfig) { + this.transformedEntries = this.entries.map((entry) => ({ + ...entry, + created: entry.created && formatEpochTime(entry.created), + updated: entry.updated && formatEpochTime(entry.updated), + })); + } +} diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.model.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.model.ts new file mode 100644 index 00000000000..5df9e6040a1 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.model.ts @@ -0,0 +1,13 @@ +import { KeyValuePair } from '../../asm-customer-360.model'; +import { TableEntry } from '../../asm-customer-ui-components/asm-customer-table/asm-customer-table.model'; + +export interface GeneralEntry extends TableEntry { + type?: string; + id?: string; + description?: string; + descriptionArgs?: Array; + category?: string; + created?: number; + updated?: number; + url?: string; +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.module.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.module.ts similarity index 75% rename from feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.module.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.module.ts index 7f9c76d52ad..60f878e3d5f 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-activity/asm-customer-activity.module.ts +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.module.ts @@ -1,6 +1,6 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { AsmCustomerTableModule } from '../asm-customer-ui-components/asm-customer-table/asm-customer-table.module'; +import { AsmCustomerTableModule } from '../../asm-customer-ui-components/asm-customer-table/asm-customer-table.module'; import { AsmCustomerActivityComponent } from './asm-customer-activity.component'; @NgModule({ diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.html b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.html new file mode 100644 index 00000000000..6cb6d34043d --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.html @@ -0,0 +1,74 @@ + +
+ 1 - {{ storeData.pagination?.pageSize }} from + {{ storeData.pagination?.totalResults }} stores found +
+ + + +
+
+
+
{{ store.displayName }}
+
+ {{ store.address.line1 }}, {{ store.address.line2 }} +
+
{{ store.address.town }}
+
+
+ {{ store.formattedDistance }} +
+
+
+ +
+
+ +
+
{{ selectedStore.displayName }}
+
{{ selectedStore.address?.line1 }}
+
{{ selectedStore.address?.line2 }}
+
{{ selectedStore.address?.town }}
+
+
+ +
+
+
+ {{ opening.key }}{{ opening.value }} +
+
+
+
+ {{ feature }} +
+
+
+
+
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.module.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.module.ts similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.module.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.module.ts diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.scss b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.scss similarity index 97% rename from feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.scss rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.scss index 19820486572..2d2dd71e99b 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.scss +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.scss @@ -52,6 +52,7 @@ cursor: pointer; display: flex; flex-direction: row; + margin-bottom: 8px; position: relative; &.selected { @@ -96,10 +97,10 @@ border-left: 1px solid #dce1e8; box-sizing: border-box; display: flex; + flex: 0 0 100px; justify-content: center; padding: 0 4px; margin-left: auto; - width: 100px; } &-text { diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.spec.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.spec.ts similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-map/asm-customer-map.component.spec.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.spec.ts diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.ts new file mode 100644 index 00000000000..3eaf50126f6 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.ts @@ -0,0 +1,78 @@ +import { HttpParams } from '@angular/common/http'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnInit, +} from '@angular/core'; +import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; +import { AsmCustomer360StoreLocation } from '@spartacus/asm/root'; +import { PointOfService } from '@spartacus/core'; +import { + StoreFinderSearchPage, + StoreFinderService, +} from '@spartacus/storefinder/core'; +import { Customer360SectionConfig } from 'feature-libs/asm/core/models/customer-360-section-config'; +import { Customer360SectionData } from 'feature-libs/asm/core/models/customer-360-section-data'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: 'cx-asm-customer-map', + templateUrl: './asm-customer-map.component.html', + styleUrls: ['./asm-customer-map.component.scss'], +}) +export class AsmCustomerMapComponent implements OnInit { + storeData: StoreFinderSearchPage; + + readonly apiKey: string | undefined; + + currentLocation: string; + + googleMapsUrl: SafeResourceUrl; + + selectedStore: PointOfService; + + constructor( + public config: Customer360SectionConfig, + protected data: Customer360SectionData, + protected changeDetectorRef: ChangeDetectorRef, + protected sanitizer: DomSanitizer, + /** TODO: This belongs in the 'storefinder' module. Should ask if we need to move these to core or some feature design. */ + protected storeFinderService: StoreFinderService + ) { + this.apiKey = config.googleMapsApiKey; + } + + ngOnInit(): void { + this.storeFinderService.findStoresAction(this.data.data.address); + + this.storeFinderService.getFindStoresEntities().subscribe((data: any) => { + if (data) { + this.storeData = data; + this.selectedStore = data.stores?.[0]; + this.changeDetectorRef.detectChanges(); + } + }); + } + + updateGoogleMapsUrl(): void { + if (this.apiKey && this.currentLocation && this.selectedStore.geoPoint) { + const coordinates = `${this.selectedStore.geoPoint.latitude},${this.selectedStore.geoPoint.longitude}`; + + const params = new HttpParams() + .append('key', this.apiKey) + .append('origin', this.currentLocation) + .append('destination', coordinates); + + this.googleMapsUrl = this.sanitizer.bypassSecurityTrustResourceUrl( + `https://www.google.com/maps/embed/v1/directions?${params.toString()}` + ); + } + } + + selectStore(store: PointOfService): void { + this.selectedStore = store; + + this.updateGoogleMapsUrl(); + } +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-map/map-types.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/map-types.ts similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-map/map-types.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/map-types.ts diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.html b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-customer-overview.component.html similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.html rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-customer-overview.component.html diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.spec.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-customer-overview.component.spec.ts similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.spec.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-customer-overview.component.spec.ts diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-customer-overview.component.ts similarity index 97% rename from feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-customer-overview.component.ts index d7df9684909..c6ddfbe7697 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.component.ts +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-customer-overview.component.ts @@ -1,7 +1,6 @@ import { Component, EventEmitter, - Input, OnDestroy, OnInit, Output, @@ -9,13 +8,7 @@ import { import { AsmConfig } from '@spartacus/asm/core'; import { Cart } from '@spartacus/cart/base/root'; -import { - ImageType, - PriceType, - Product, - UrlCommand, - User, -} from '@spartacus/core'; +import { ImageType, PriceType, Product, UrlCommand } from '@spartacus/core'; import { BREAKPOINT, BreakpointService } from '@spartacus/storefront'; import { Observable, of, Subscription } from 'rxjs'; @@ -27,8 +20,6 @@ import { AsmInterestEntry } from './asm-customer-overview.model'; templateUrl: './asm-customer-overview.component.html', }) export class AsmCustomerOverviewComponent implements OnInit, OnDestroy { - @Input() customer: User; - @Output() navigate: EventEmitter = new EventEmitter(); @@ -62,11 +53,9 @@ export class AsmCustomerOverviewComponent implements OnInit, OnDestroy { } ngOnInit(): void { - if (this.customer?.uid) { - this.activeCart$ = of(this.getMockCartData()); - this.savedCart$ = of(this.getMockCartData()); - this.interests$ = of(this.getMockInterestData()); - } + this.activeCart$ = of(this.getMockCartData()); + this.savedCart$ = of(this.getMockCartData()); + this.interests$ = of(this.getMockInterestData()); } onSelectProduct(selectedProduct: Product): void { @@ -163,15 +152,7 @@ export class AsmCustomerOverviewComponent implements OnInit, OnDestroy { url: '/Open-Catalogue/Components/Power-Supplies/Power-Adapters-%26-Inverters/ACK-E2/p/514518', }; return { - products: [ - product, - product, - product, - product, - product, - product, - product, - ], + products: [product, product, product, product, product, product, product], }; } diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.model.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-customer-overview.model.ts similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.model.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-customer-overview.model.ts diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.module.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-customer-overview.module.ts similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-customer-overview.module.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-customer-overview.module.ts diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.html b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-product-item/asm-product-item.component.html similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.html rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-product-item/asm-product-item.component.html diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.spec.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-product-item/asm-product-item.component.spec.ts similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.spec.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-product-item/asm-product-item.component.spec.ts diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-product-item/asm-product-item.component.ts similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-overview/asm-product-item/asm-product-item.component.ts diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.html b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.html new file mode 100644 index 00000000000..77fbc741c5b --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.html @@ -0,0 +1,8 @@ + diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.module.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.module.ts new file mode 100644 index 00000000000..9b9702cd1f2 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.module.ts @@ -0,0 +1,12 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; + +import { AsmCustomerTableModule } from '../../asm-customer-ui-components/asm-customer-table/asm-customer-table.module'; +import { AsmCustomerProductReviewsComponent } from './asm-customer-product-reviews.component'; + +@NgModule({ + imports: [CommonModule, AsmCustomerTableModule], + declarations: [AsmCustomerProductReviewsComponent], + exports: [AsmCustomerProductReviewsComponent], +}) +export class AsmCustomerProductReviewsComponentModule {} diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.spec.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.spec.ts new file mode 100644 index 00000000000..e5aa8754ce6 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AsmCustomerProductReviewsComponent } from './asm-customer-product-reviews.component'; + +describe('AsmCustomerProductReviewsComponent', () => { + let component: AsmCustomerProductReviewsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [AsmCustomerProductReviewsComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AsmCustomerProductReviewsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.ts new file mode 100644 index 00000000000..58b1956d8bf --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.ts @@ -0,0 +1,44 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { Customer360SectionConfig } from '@spartacus/asm/core'; +import { AsmCustomer360ReviewList } from '@spartacus/asm/root'; +import { SemanticPathService } from '@spartacus/core'; +import { Customer360SectionData } from 'feature-libs/asm/core/models/customer-360-section-data'; + +import { combineStrings, formatEpochTime } from '../../asm-customer-360.utils'; +import { CustomerTableColumn } from '../../asm-customer-ui-components/asm-customer-table/asm-customer-table.model'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: 'cx-asm-customer-product-reviews', + templateUrl: './asm-customer-product-reviews.component.html', +}) +export class AsmCustomerProductReviewsComponent { + reviewColumns: Array = [ + { property: 'item', text: 'item' }, + { property: 'dateAndStatus', text: 'DATE / STATUS' }, + { property: 'rating', text: 'rate', renderAsStarRating: true }, + { property: 'reviewText', text: 'review' }, + ]; + + reviewEntries: Array; + + constructor( + public config: Customer360SectionConfig, + protected sectionData: Customer360SectionData, + /** TODO: Importing this seems questionable. */ + protected semanticPathService: SemanticPathService + ) { + this.reviewEntries = sectionData.data.reviews.map((entry) => ({ + ...entry, + item: combineStrings(entry.productName, entry.productCode, ', SKU: '), + dateAndStatus: combineStrings( + entry.createdAt + ? formatEpochTime(Number(new Date(entry.createdAt))) + : undefined, + entry.reviewStatus, + ' / ' + ), + // productUrl: this.semanticPathService.transform({ cxRoute: 'product', params: { code: entry.productCode, slug: entry.productName } }), + })); + } +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.html b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.component.html similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.html rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.component.html diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.spec.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.component.spec.ts similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.spec.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.component.spec.ts diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.component.ts similarity index 87% rename from feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.component.ts index 44ab07e0651..ddc8c6b3618 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.component.ts +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.component.ts @@ -1,12 +1,7 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { AsmConfig } from '@spartacus/asm/core'; -import { - Address, - PaymentDetails, - TranslationService, - User, -} from '@spartacus/core'; +import { Address, PaymentDetails, TranslationService } from '@spartacus/core'; import { BREAKPOINT, BreakpointService, @@ -25,8 +20,6 @@ import { CustomerProfileData } from './asm-customer-profile.model'; templateUrl: './asm-customer-profile.component.html', }) export class AsmCustomerProfileComponent implements OnInit { - @Input() customer: User; - focusConfig: FocusConfig = { trap: true, block: true, @@ -58,28 +51,26 @@ export class AsmCustomerProfileComponent implements OnInit { ngOnInit(): void { this.getSamplePaymentMethods(); - if (this.customer?.uid) { - this.customerProfileData$ = forkJoin([ - of(this.getSamplePaymentMethods()), - of(this.getAddresses()), - ]).pipe( - map(([paymentDetails, addresses]) => { - const defaultPaymentDetail = paymentDetails.find( - (paymentDetail) => paymentDetail.defaultPayment - ); - const deliveryAddress = addresses.find( - (address) => address.defaultAddress - ); - return { - billingAddress: defaultPaymentDetail?.billingAddress, - deliveryAddress: deliveryAddress, - phone1: deliveryAddress?.phone, - phone2: deliveryAddress?.cellphone, - paymentInfoList: paymentDetails, - }; - }) - ); - } + this.customerProfileData$ = forkJoin([ + of(this.getSamplePaymentMethods()), + of(this.getAddresses()), + ]).pipe( + map(([paymentDetails, addresses]) => { + const defaultPaymentDetail = paymentDetails.find( + (paymentDetail) => paymentDetail.defaultPayment + ); + const deliveryAddress = addresses.find( + (address) => address.defaultAddress + ); + return { + billingAddress: defaultPaymentDetail?.billingAddress, + deliveryAddress: deliveryAddress, + phone1: deliveryAddress?.phone, + phone2: deliveryAddress?.cellphone, + paymentInfoList: paymentDetails, + }; + }) + ); } getCardContent({ diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.model.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.model.ts similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.model.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.model.ts diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.module.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.module.ts similarity index 100% rename from feature-libs/asm/components/asm-customer-360/asm-customer-profile/asm-customer-profile.module.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.module.ts diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-promotions/asm-customer-promotions.component.html b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-promotions/asm-customer-promotions.component.html new file mode 100644 index 00000000000..91016012d82 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-promotions/asm-customer-promotions.component.html @@ -0,0 +1,24 @@ + + + + + diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-promotions/asm-customer-promotions.component.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-promotions/asm-customer-promotions.component.ts new file mode 100644 index 00000000000..4223e014544 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-promotions/asm-customer-promotions.component.ts @@ -0,0 +1,165 @@ +import { Component } from '@angular/core'; +import { + CustomerListingButton, + CustomerListingTab, + ListingItem, +} from '../../asm-customer-ui-components/asm-customer-listing/asm-customer-listing.model'; + +@Component({ + selector: 'cx-asm-customer-promotions', + templateUrl: './asm-customer-promotions.component.html', +}) +export class AsmCustomerPromotionsComponent { + couponItems = [ + { + code: 'This is a 20% off coupon', + name: 'This is a 20% off coupon and you are gonna be so happy with these savings', + couponApplied: false, + }, + { + code: 'This is a 1% off coupon', + name: "This is a 1% off coupon and you won't really notice I'm here", + couponApplied: true, + }, + ]; + + transformedCouponItems: Array; + + couponButtonConfig: CustomerListingButton = { + applyText: 'Apply To Cart', + appliedText: 'Coupon Applied', + removeText: 'Remove', + isApplied: (item) => item.applied, + }; + + promotionItems = [ + { + name: 'This is a 20% off coupon', + firedMessage: + 'This is a 20% off coupon and you are gonna be so happy with these savings', + fired: true, + }, + { + name: 'This is a 1% off coupon', + firedMessage: + "This is a 1% off coupon and you won't really notice I'm here", + fired: false, + }, + ]; + + transformedPromotionItems: Array; + + customerCouponItems = [ + { + code: 'This is a 20% off coupon', + name: 'This is a 20% off coupon and you are gonna be so happy with these savings', + couponApplied: true, + }, + { + code: 'This is a 1% off coupon', + name: "This is a 1% off coupon and you won't really notice I'm here", + couponApplied: false, + }, + { + code: 'This is a 500% off coupon', + name: 'This is a 500% off coupon and you are gonna be so happy with these earnings', + couponApplied: false, + }, + { + code: 'This is a 0% off coupon', + name: "This is a 0% off coupon and you literally won't notice I'm here", + couponApplied: true, + }, + { + code: 'This is a 100% off coupon', + name: "It's free, what else do you want?", + couponApplied: false, + }, + { + code: 'This is a 66.666666% off coupon', + name: "This is the devil's discount. By accepting this, you accept Satan as your lord and savior :)", + couponApplied: true, + }, + ]; + + customerCouponTabs: Array; + + currentCustomerCouponTabIndex = 0; + + constructor() { + this.transformedCouponItems = this.couponItems.map((item) => ({ + title: item.code, + description: item.name, + applied: item.couponApplied, + })); + + this.transformedPromotionItems = this.promotionItems.map((item) => ({ + title: item.name, + description: item.firedMessage, + applied: item.fired, + })); + + this.setCustomerCouponTabs(); + } + + applyCoupon(coupon: ListingItem): void { + coupon.applied = !coupon.applied; + } + + applyCustomerCoupon(coupon: ListingItem): void { + coupon.applied = !coupon.applied; + + if (coupon.applied) { + this.customerCouponTabs[0].items = + this.customerCouponTabs[0].items.filter((item) => item !== coupon); + this.customerCouponTabs[1].items = + this.customerCouponTabs[1].items.concat(coupon); + } else { + this.customerCouponTabs[1].items = + this.customerCouponTabs[1].items.filter((item) => item !== coupon); + this.customerCouponTabs[0].items = + this.customerCouponTabs[0].items.concat(coupon); + } + } + + onCustomerCouponTabClick(customerCouponTab: CustomerListingTab): void { + this.currentCustomerCouponTabIndex = this.customerCouponTabs.findIndex( + (tab) => tab === customerCouponTab + ); + } + + setCustomerCouponTabs(): void { + this.customerCouponTabs = [ + { + tabName: 'AVAILABLE', + items: [], + emptyText: 'There are currently no available customer coupons.', + buttonConfig: { + applyText: 'Send to customer', + }, + }, + { + tabName: 'SENT', + items: [], + emptyText: 'There are currently no sent customer coupons.', + buttonConfig: { + applyText: 'Remove', + }, + }, + ]; + + this.customerCouponItems.forEach((item) => { + const listingItem = { + title: item.code, + description: item.name, + applied: item.couponApplied, + }; + + if (item.couponApplied) { + this.customerCouponTabs[1].items.push(listingItem); + } else { + this.customerCouponTabs[0].items.push(listingItem); + } + }); + } +} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.module.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-promotions/asm-customer-promotions.module.ts similarity index 75% rename from feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.module.ts rename to feature-libs/asm/components/asm-customer-360/sections/asm-customer-promotions/asm-customer-promotions.module.ts index 95a4f2a963e..2a45c3f3055 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-promotions/asm-customer-promotions.module.ts +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-promotions/asm-customer-promotions.module.ts @@ -1,6 +1,6 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { AsmCustomerListingModule } from '../asm-customer-ui-components/asm-customer-listing/asm-customer-listing.module'; +import { AsmCustomerListingModule } from '../../asm-customer-ui-components/asm-customer-listing/asm-customer-listing.module'; import { AsmCustomerPromotionsComponent } from './asm-customer-promotions.component'; @NgModule({ diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.html b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.html new file mode 100644 index 00000000000..b7ae127f20f --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.html @@ -0,0 +1,8 @@ + diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.module.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.module.ts new file mode 100644 index 00000000000..2e9d59b08e8 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.module.ts @@ -0,0 +1,12 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; + +import { AsmCustomerTableModule } from '../../asm-customer-ui-components/asm-customer-table/asm-customer-table.module'; +import { AsmCustomerSupportTicketsComponent } from './asm-customer-support-tickets.component'; + +@NgModule({ + imports: [CommonModule, AsmCustomerTableModule], + declarations: [AsmCustomerSupportTicketsComponent], + exports: [AsmCustomerSupportTicketsComponent], +}) +export class AsmCustomerSupportTicketsComponentModule {} diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.spec.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.spec.ts new file mode 100644 index 00000000000..dba127f3a16 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AsmCustomerSupportTicketsComponent } from './asm-customer-support-tickets.component'; + +describe('AsmCustomerSupportTicketsComponent', () => { + let component: AsmCustomerSupportTicketsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [AsmCustomerSupportTicketsComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AsmCustomerSupportTicketsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.ts new file mode 100644 index 00000000000..6e42c2177fd --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.ts @@ -0,0 +1,84 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { Customer360SectionConfig } from '@spartacus/asm/core'; + +import { formatEpochTime, replaceTokens } from '../../asm-customer-360.utils'; +import { CustomerTableColumn } from '../../asm-customer-ui-components/asm-customer-table/asm-customer-table.model'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: 'cx-asm-customer-support-tickets', + templateUrl: './asm-customer-support-tickets.component.html', +}) +export class AsmCustomerSupportTicketsComponent { + ticketColumns: Array = [ + { property: 'id', text: 'id' }, + { property: 'headline', text: 'headline' }, + { property: 'type', text: 'category' }, + { property: 'category', text: 'status' }, + { property: 'created', text: 'created' }, + { property: 'updated', text: 'updated' }, + ]; + + ticketEntries = [ + { + type: 'Enquiry', + id: '00000001', + description: 'Can thing work this way instead?', + created: Number(new Date('2022-07-02T18:25:43.511Z')), + updated: Number(new Date('2022-07-04T18:25:43.511Z')), + category: 'New', + }, + { + type: 'Complaint', + id: '00000002', + description: 'Thing not work', + created: Number(new Date('2022-07-04T18:25:43.511Z')), + updated: Number(new Date('2022-07-05T18:25:43.511Z')), + category: 'Closed', + }, + { + type: 'Problem', + id: '00000003', + description: 'Thing work but super slow', + created: Number(new Date('2022-06-30T18:25:43.511Z')), + updated: Number(new Date('2022-07-01T18:25:43.511Z')), + category: 'Addressed', + }, + { + type: 'Enquiry', + id: '00000004', + description: 'Why thing work this way?', + created: Number(new Date('2022-05-30T18:25:43.511Z')), + updated: Number(new Date('2022-06-10T18:25:43.511Z')), + category: 'New', + }, + { + type: 'Complaint', + id: '00000005', + description: 'Thing work but so slow ${cool}', + descriptionArgs: [{ key: 'cool', value: 'as hell' }], + created: Number(new Date('2022-07-03T18:25:43.511Z')), + updated: Number(new Date('2022-07-06T18:25:43.511Z')), + category: 'Closed', + }, + { + type: 'Problem', + id: '00000006', + description: 'Thing not work as expected', + created: Number(new Date('2022-06-30T18:25:43.511Z')), + updated: Number(new Date('2022-07-01T18:25:43.511Z')), + category: 'Addressed', + }, + ]; + + transformedTicketEntries: Array; + + constructor(public config: Customer360SectionConfig) { + this.transformedTicketEntries = this.ticketEntries.map((entry) => ({ + ...entry, + headline: replaceTokens(entry.description, entry.descriptionArgs), + created: entry.created && formatEpochTime(entry.created), + updated: entry.updated && formatEpochTime(entry.updated), + })); + } +} diff --git a/feature-libs/asm/components/asm-customer-360/sections/components.ts b/feature-libs/asm/components/asm-customer-360/sections/components.ts new file mode 100644 index 00000000000..cebd1ef694f --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/components.ts @@ -0,0 +1,13 @@ +export { AsmCustomerActivityComponent } from './asm-customer-activity/asm-customer-activity.component'; + +export { AsmCustomerMapComponent } from './asm-customer-map/asm-customer-map.component'; + +export { AsmCustomerOverviewComponent } from './asm-customer-overview/asm-customer-overview.component'; + +export { AsmCustomerProductReviewsComponent } from './asm-customer-product-reviews/asm-customer-product-reviews.component'; + +export { AsmCustomerProfileComponent } from './asm-customer-profile/asm-customer-profile.component'; + +export { AsmCustomerPromotionsComponent } from './asm-customer-promotions/asm-customer-promotions.component'; + +export { AsmCustomerSupportTicketsComponent } from './asm-customer-support-tickets/asm-customer-support-tickets.component'; diff --git a/feature-libs/asm/components/asm-customer-360/sections/customer-360-section-context-source.model.ts b/feature-libs/asm/components/asm-customer-360/sections/customer-360-section-context-source.model.ts new file mode 100644 index 00000000000..dad9d104a5a --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/customer-360-section-context-source.model.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@angular/core'; +import { User } from '@spartacus/core'; +import { ReplaySubject } from 'rxjs'; + +import { Customer360SectionContext } from './customer-360-section-context.model'; + +@Injectable() +export class Customer360SectionContextSource extends Customer360SectionContext { + readonly customer$ = new ReplaySubject(1); +} diff --git a/feature-libs/asm/components/asm-customer-360/sections/customer-360-section-context.model.ts b/feature-libs/asm/components/asm-customer-360/sections/customer-360-section-context.model.ts new file mode 100644 index 00000000000..7f7c61c5e29 --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/sections/customer-360-section-context.model.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@angular/core'; +import { User } from '@spartacus/core'; +import { Observable } from 'rxjs'; + +@Injectable() +export abstract class Customer360SectionContext { + readonly customer$: Observable; +} diff --git a/feature-libs/asm/core/asm-core.module.ts b/feature-libs/asm/core/asm-core.module.ts index 13603645e08..aca351f041a 100644 --- a/feature-libs/asm/core/asm-core.module.ts +++ b/feature-libs/asm/core/asm-core.module.ts @@ -7,6 +7,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { MODULE_INITIALIZER } from '@spartacus/core'; +import { StoreFinderModule } from '@spartacus/storefinder'; import { AsmConnector } from './connectors/asm.connector'; import { facadeProviders } from './facade/facade-providers'; import { AsmStatePersistenceService } from './services/asm-state-persistence.service'; @@ -20,7 +21,7 @@ export function asmStatePersistenceFactory( } @NgModule({ - imports: [CommonModule, AsmStoreModule], + imports: [CommonModule, AsmStoreModule, StoreFinderModule], providers: [ AsmConnector, { diff --git a/feature-libs/asm/core/connectors/asm.adapter.ts b/feature-libs/asm/core/connectors/asm.adapter.ts index 48b964de139..9b866d1f3c1 100644 --- a/feature-libs/asm/core/connectors/asm.adapter.ts +++ b/feature-libs/asm/core/connectors/asm.adapter.ts @@ -5,11 +5,15 @@ */ import { BindCartParams, CustomerListsPage } from '@spartacus/asm/root'; -import { Observable } from 'rxjs'; import { - CustomerSearchOptions, - CustomerSearchPage, -} from '../models/asm.models'; + AsmCustomer360Params, + AsmCustomer360Response, +} from '@spartacus/asm/root'; +import { AsmCustomer360Query } from '@spartacus/asm/root'; +import { Observable } from 'rxjs'; + +import { CustomerSearchOptions, CustomerSearchPage } from '../models/asm.models'; + export abstract class AsmAdapter { /** * Abstract function used to search for customers. @@ -27,4 +31,14 @@ export abstract class AsmAdapter { * Used to bind an anonymous cart to a registered user. */ abstract bindCart(options: BindCartParams): Observable; + + /** + * Fetches data needed for certain ASM components. + * @param queries that contain information on the specific UI component. + * @param options with the emulated user's ID. + */ + abstract getCustomer360Data( + queries: Array, + options: AsmCustomer360Params + ): Observable; } diff --git a/feature-libs/asm/core/connectors/asm.connector.ts b/feature-libs/asm/core/connectors/asm.connector.ts index 733684ce6c4..e9afcfa258c 100644 --- a/feature-libs/asm/core/connectors/asm.connector.ts +++ b/feature-libs/asm/core/connectors/asm.connector.ts @@ -6,11 +6,14 @@ import { Injectable } from '@angular/core'; import { BindCartParams, CustomerListsPage } from '@spartacus/asm/root'; -import { Observable } from 'rxjs'; import { - CustomerSearchOptions, - CustomerSearchPage, -} from '../models/asm.models'; + AsmCustomer360Params, + AsmCustomer360Query, + AsmCustomer360Response, +} from '@spartacus/asm/root'; +import { Observable } from 'rxjs'; + +import { CustomerSearchOptions, CustomerSearchPage } from '../models/asm.models'; import { AsmAdapter } from './asm.adapter'; @Injectable({ @@ -32,4 +35,11 @@ export class AsmConnector { bindCart(options: BindCartParams): Observable { return this.asmAdapter.bindCart(options); } + + getCustomer360Data( + queries: Array, + options: AsmCustomer360Params + ): Observable { + return this.asmAdapter.getCustomer360Data(queries, options); + } } diff --git a/feature-libs/asm/core/facade/asm.service.ts b/feature-libs/asm/core/facade/asm.service.ts index aabe6deca2d..4e1a1ff8d6a 100644 --- a/feature-libs/asm/core/facade/asm.service.ts +++ b/feature-libs/asm/core/facade/asm.service.ts @@ -6,6 +6,7 @@ import { Injectable } from '@angular/core'; import { select, Store } from '@ngrx/store'; +import { AsmCustomer360Params, AsmCustomer360Query, AsmCustomer360Response } from '@spartacus/asm/root'; import { Observable } from 'rxjs'; import { AsmUi, @@ -98,4 +99,15 @@ export class AsmService { getAsmUiState(): Observable { return this.store.pipe(select(AsmSelectors.getAsmUi)); } + + fetchCustomer360Data( + queries: Array, + options: AsmCustomer360Params + ): void { + this.store.dispatch(new AsmActions.Customer360Get([queries, options])); + } + + getCustomer360Data(): Observable { + return this.store.pipe(select(AsmSelectors.getCustomer360Data)); + } } diff --git a/feature-libs/asm/core/store/actions/customer.action.ts b/feature-libs/asm/core/store/actions/customer.action.ts index 99fcfdf392c..896f94b4032 100644 --- a/feature-libs/asm/core/store/actions/customer.action.ts +++ b/feature-libs/asm/core/store/actions/customer.action.ts @@ -4,12 +4,14 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { AsmCustomer360Params, AsmCustomer360Query, AsmCustomer360Response } from '@spartacus/asm/root'; import { StateUtils } from '@spartacus/core'; import { CustomerSearchOptions, CustomerSearchPage, } from '../../models/asm.models'; import { + CUSTOMER_360_DATA, CUSTOMER_LIST_CUSTOMERS_SEARCH_DATA, CUSTOMER_SEARCH_DATA, } from '../asm-state'; @@ -28,6 +30,11 @@ export const CUSTOMER_LIST_CUSTOMERS_SEARCH_SUCCESS = export const CUSTOMER_LIST_CUSTOMERS_SEARCH_RESET = '[Asm] Customer List Customers Search Reset'; +export const CUSTOMER_360_GET = '[Asm] Customer 360 Data Get'; +export const CUSTOMER_360_GET_FAIL = '[Asm] Customer 360 Data Get Fail'; +export const CUSTOMER_360_GET_SUCCESS = '[Asm] Customer 360 Data Get Success'; +export const CUSTOMER_360_GET_RESET = '[Asm] Customer 360 Data Get Reset'; + export class CustomerSearch extends StateUtils.LoaderLoadAction { readonly type = CUSTOMER_SEARCH; constructor(public payload: CustomerSearchOptions) { @@ -84,6 +91,36 @@ export class CustomerListCustomersSearchReset extends StateUtils.LoaderResetActi } } +export class Customer360Get extends StateUtils.LoaderLoadAction { + readonly type = CUSTOMER_360_GET; + constructor( + public payload: [Array, AsmCustomer360Params] + ) { + super(CUSTOMER_360_DATA); + } +} + +export class Customer360GetFail extends StateUtils.LoaderFailAction { + readonly type = CUSTOMER_360_GET_FAIL; + constructor(public payload: any) { + super(CUSTOMER_360_DATA); + } +} + +export class Customer360GetSuccess extends StateUtils.LoaderSuccessAction { + readonly type = CUSTOMER_360_GET_SUCCESS; + constructor(public payload: AsmCustomer360Response) { + super(CUSTOMER_360_DATA); + } +} + +export class Customer360GetReset extends StateUtils.LoaderResetAction { + readonly type = CUSTOMER_360_GET_RESET; + constructor() { + super(CUSTOMER_360_DATA); + } +} + // action types export type CustomerAction = | CustomerSearch @@ -93,4 +130,8 @@ export type CustomerAction = | CustomerListCustomersSearch | CustomerListCustomersSearchFail | CustomerListCustomersSearchSuccess - | CustomerListCustomersSearchReset; + | CustomerListCustomersSearchReset + | Customer360Get + | Customer360GetFail + | Customer360GetSuccess + | Customer360GetReset; diff --git a/feature-libs/asm/core/store/asm-state.ts b/feature-libs/asm/core/store/asm-state.ts index f4480174d62..da13f4ead88 100644 --- a/feature-libs/asm/core/store/asm-state.ts +++ b/feature-libs/asm/core/store/asm-state.ts @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ - +import { AsmCustomer360Response } from '@spartacus/asm/root'; import { StateUtils } from '@spartacus/core'; import { AsmUi, CustomerSearchPage } from '../models/asm.models'; @@ -11,6 +11,7 @@ export const ASM_FEATURE = 'asm'; export const CUSTOMER_SEARCH_DATA = '[asm] Customer search data'; export const CUSTOMER_LIST_CUSTOMERS_SEARCH_DATA = '[asm] Customer list customers search data'; +export const CUSTOMER_360_DATA = '[asm] Customer 360 data'; export interface StateWithAsm { [ASM_FEATURE]: AsmState; @@ -19,5 +20,6 @@ export interface StateWithAsm { export interface AsmState { customerSearchResult: StateUtils.LoaderState; customerListCustomersSearchResult: StateUtils.LoaderState; + customer360Response: StateUtils.LoaderState; asmUi: AsmUi; } diff --git a/feature-libs/asm/core/store/effects/customer.effect.ts b/feature-libs/asm/core/store/effects/customer.effect.ts index fb2937dc5ac..8c6f919b827 100644 --- a/feature-libs/asm/core/store/effects/customer.effect.ts +++ b/feature-libs/asm/core/store/effects/customer.effect.ts @@ -6,6 +6,9 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { + AsmCustomer360Response, +} from '@spartacus/asm/root'; import { normalizeHttpError } from '@spartacus/core'; import { Observable, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; @@ -56,5 +59,22 @@ export class CustomerEffects { ) ); + customer360Data$: Observable = createEffect(() => + this.actions$.pipe( + ofType(AsmActions.CUSTOMER_360_GET), + map((action: AsmActions.Customer360Get) => action.payload), + switchMap((params) => + this.asmConnector.getCustomer360Data(...params).pipe( + map((customer360Response: AsmCustomer360Response) => { + return new AsmActions.Customer360GetSuccess(customer360Response); + }), + catchError((error) => + of(new AsmActions.Customer360GetFail(normalizeHttpError(error))) + ) + ) + ) + ) + ); + constructor(private actions$: Actions, private asmConnector: AsmConnector) {} } diff --git a/feature-libs/asm/core/store/reducers/index.ts b/feature-libs/asm/core/store/reducers/index.ts index 2c682e7e010..0b71ecbdc99 100644 --- a/feature-libs/asm/core/store/reducers/index.ts +++ b/feature-libs/asm/core/store/reducers/index.ts @@ -11,12 +11,14 @@ import { ActionReducerMap, MetaReducer, } from '@ngrx/store'; +import { AsmCustomer360Response } from '@spartacus/asm/root'; import { StateUtils } from '@spartacus/core'; import { CustomerSearchPage } from '../../models/asm.models'; import { AsmActions } from '../actions'; import { AsmState, CUSTOMER_LIST_CUSTOMERS_SEARCH_DATA, + CUSTOMER_360_DATA, CUSTOMER_SEARCH_DATA, } from '../asm-state'; import * as fromAsmUiReducer from './asm-ui.reducer'; @@ -29,6 +31,8 @@ export function getReducers(): ActionReducerMap { StateUtils.loaderReducer( CUSTOMER_LIST_CUSTOMERS_SEARCH_DATA ), + customer360Response: + StateUtils.loaderReducer(CUSTOMER_360_DATA), asmUi: fromAsmUiReducer.reducer, }; } diff --git a/feature-libs/asm/core/store/selectors/asm-group.selectors.ts b/feature-libs/asm/core/store/selectors/asm-group.selectors.ts index 29ef525e31f..e9679919fb9 100644 --- a/feature-libs/asm/core/store/selectors/asm-group.selectors.ts +++ b/feature-libs/asm/core/store/selectors/asm-group.selectors.ts @@ -5,5 +5,6 @@ */ export * from './asm-ui.selectors'; +export * from './customer-360.selectors'; export * from './customer-search.selectors'; export * from './feature.selector'; diff --git a/feature-libs/asm/core/store/selectors/customer-360.selectors.ts b/feature-libs/asm/core/store/selectors/customer-360.selectors.ts new file mode 100644 index 00000000000..ab2fdec7ace --- /dev/null +++ b/feature-libs/asm/core/store/selectors/customer-360.selectors.ts @@ -0,0 +1,28 @@ +import { createSelector, MemoizedSelector } from '@ngrx/store'; +import { AsmCustomer360Response } from '@spartacus/asm/root'; +import { StateUtils } from '@spartacus/core'; +import { AsmState, StateWithAsm } from '../asm-state'; +import { getAsmState } from './feature.selector'; + +export const getCustomer360DataLoaderState: MemoizedSelector< + StateWithAsm, + StateUtils.LoaderState +> = createSelector(getAsmState, (state: AsmState) => state.customer360Response); + +export const getCustomer360Data: MemoizedSelector< + StateWithAsm, + AsmCustomer360Response +> = createSelector( + getCustomer360DataLoaderState, + (state: StateUtils.LoaderState) => + StateUtils.loaderValueSelector(state) +); + +export const getCustomer360DataLoading: MemoizedSelector< + StateWithAsm, + boolean +> = createSelector( + getCustomer360DataLoaderState, + (state: StateUtils.LoaderState) => + StateUtils.loaderLoadingSelector(state) +); diff --git a/feature-libs/asm/occ/adapters/default-occ-asm-config.ts b/feature-libs/asm/occ/adapters/default-occ-asm-config.ts index e1edaccfefc..0836756f752 100644 --- a/feature-libs/asm/occ/adapters/default-occ-asm-config.ts +++ b/feature-libs/asm/occ/adapters/default-occ-asm-config.ts @@ -13,6 +13,8 @@ export const defaultOccAsmConfig: OccConfig = { asmCustomerSearch: '/assistedservicewebservices/customers/search', asmCustomerLists: '/assistedservicewebservices/customerlists', asmBindCart: '/assistedservicewebservices/bind-cart', + asmCustomer360: + '/assistedservicewebservices/${baseSiteId}/users/${userId}/customer360', }, }, }, diff --git a/feature-libs/asm/occ/adapters/occ-asm.adapter.ts b/feature-libs/asm/occ/adapters/occ-asm.adapter.ts index 4b6c6b7c853..0744a9e9c69 100644 --- a/feature-libs/asm/occ/adapters/occ-asm.adapter.ts +++ b/feature-libs/asm/occ/adapters/occ-asm.adapter.ts @@ -14,7 +14,7 @@ import { CUSTOMER_LISTS_NORMALIZER, CUSTOMER_SEARCH_PAGE_NORMALIZER, } from '@spartacus/asm/core'; -import { BindCartParams, CustomerListsPage } from '@spartacus/asm/root'; +import { AsmCustomer360Response, AsmCustomer360Type, BindCartParams, CustomerListsPage } from '@spartacus/asm/root'; import { BaseSiteService, ConverterService, @@ -25,6 +25,8 @@ import { } from '@spartacus/core'; import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; +// +import { of } from 'rxjs'; @Injectable() export class OccAsmAdapter implements AsmAdapter { @@ -143,4 +145,122 @@ export class OccAsmAdapter implements AsmAdapter { .post(url, {}, { headers, params }) .pipe(catchError((error) => throwError(normalizeHttpError(error)))); } + + getCustomer360Data(/* queries: Array, { userId }: AsmCustomer360Params */): Observable { + /* + const headers = InterceptorUtil.createHeader( + USE_CUSTOMER_SUPPORT_AGENT_TOKEN, + true, + new HttpHeaders() + ); + + const url = this.occEndpointsService.buildUrl( + 'asmCustomer360', + { + urlParams: { + baseSiteId: this.activeBaseSite, + userId, + }, + }, + { + baseSite: false, + prefix: false, + } + ); + + const requestBody = { + customer360Queries: queries, + }; + */ + + // return this.http.post(url, requestBody, { headers }); + + const response: AsmCustomer360Response = { + value: [ + { + type: AsmCustomer360Type.REVIEW_LIST, + reviews: [ + { + productName: 'DC Car Battery Adapter', + productCode: '107701', + rating: '0.5', + reviewStatus: 'pending', + reviewText: 'Adapter? More like Badapter!!!', + createdAt: '2022-05-15T18:25:43.511Z', + updatedAt: '', + }, + { + productName: 'VCT-D580RM Remote Control Tripod', + productCode: '2992', + rating: '4.5', + reviewStatus: 'pending', + reviewText: 'Flimsy stand!', + createdAt: '2022-06-22T18:25:43.511Z', + updatedAt: '', + }, + { + productName: 'Mini T-Cam', + productCode: '458542', + rating: '1', + reviewStatus: 'pending', + reviewText: 'Webcam bad', + createdAt: '2022-07-02T18:25:43.511Z', + updatedAt: '', + }, + { + productName: 'HDR-CX105E Red', + productCode: '1934406', + rating: '1.5', + reviewStatus: 'pending', + reviewText: 'First review', + createdAt: '2022-07-03T18:25:43.511Z', + updatedAt: '', + }, + { + productName: 'DC Car Battery Adapter', + productCode: '10770', + rating: '2.5', + reviewStatus: 'pending', + reviewText: 'Adapter? More like Badapter!!!', + createdAt: '2022-05-15T18:25:43.511Z', + updatedAt: '', + }, + { + productName: 'VCT-D580RM Remote Control Tripod', + productCode: '29925', + rating: '3.5', + reviewStatus: 'pending', + reviewText: 'Flimsy stand!', + createdAt: '2022-06-22T18:25:43.511Z', + updatedAt: '', + }, + { + productName: 'Mini T-Cam', + productCode: '4585', + rating: '4', + reviewStatus: 'pending', + reviewText: 'Webcam bad', + createdAt: '2022-07-02T18:25:43.511Z', + updatedAt: '', + }, + { + productName: 'HDR-CX105E Red', + productCode: '19406', + rating: '3', + reviewStatus: 'pending', + reviewText: 'First review', + createdAt: '2022-07-03T18:25:43.511Z', + updatedAt: '', + }, + ], + }, + { + type: AsmCustomer360Type.STORE_LOCATION, + address: 'New York United States 10001', + }, + ], + }; + + return of(response); + } } diff --git a/feature-libs/asm/occ/model/occ-asm-endpoints.model.ts b/feature-libs/asm/occ/model/occ-asm-endpoints.model.ts index 91c87e3ed84..a684463f2e4 100644 --- a/feature-libs/asm/occ/model/occ-asm-endpoints.model.ts +++ b/feature-libs/asm/occ/model/occ-asm-endpoints.model.ts @@ -16,5 +16,6 @@ declare module '@spartacus/core' { asmCustomerSearch?: string | OccEndpoint; asmCustomerLists?: string | OccEndpoint; asmBindCart?: string | OccEndpoint; + asmCustomer360?: string | OccEndpoint; } } diff --git a/feature-libs/asm/root/config/asm-config.ts b/feature-libs/asm/root/config/asm-config.ts index e85d1bed00e..c08a2602c37 100644 --- a/feature-libs/asm/root/config/asm-config.ts +++ b/feature-libs/asm/root/config/asm-config.ts @@ -5,6 +5,8 @@ */ import { User } from '@spartacus/core'; import { ICON_TYPE } from '@spartacus/storefront'; + +import { AsmCustomer360TabsConfig } from '../model/customer-360-tabs-config'; import { CustomerListColumnActionType } from '../model/customer-list.model'; export abstract class AsmConfig { @@ -40,25 +42,7 @@ export abstract class AsmConfig { */ enable?: boolean; }; - customer360?: { - activityTab?: { - pageSize?: number; - }; - feedbackTab?: { - supportTickets?: { - pageSize?: number; - }; - productReviews?: { - pageSize?: number; - }; - }; - mapsTab?: { - googleMaps?: { - apiKey?: string; - }; - pageSize?: number; - }; - }; + customer360?: AsmCustomer360TabsConfig; }; } diff --git a/feature-libs/asm/root/config/default-asm-config.ts b/feature-libs/asm/root/config/default-asm-config.ts index 07b5f2c1b81..79d39aeb156 100644 --- a/feature-libs/asm/root/config/default-asm-config.ts +++ b/feature-libs/asm/root/config/default-asm-config.ts @@ -50,23 +50,43 @@ export const defaultAsmConfig: AsmConfig = { ], }, customer360: { - activityTab: { - pageSize: 10, - }, - feedbackTab: { - supportTickets: { - pageSize: 5, + tabs: [ + { + i18nNameKey: 'asm.customer360.overviewTab', + components: [ + { + component: AsmCustomerOverviewComponent, + }, + ], }, - productReviews: { - pageSize: 5, + { + i18nNameKey: 'asm.customer360.overviewTab', + components: [ + { + component: AsmCustomerProductReviewsComponent, + requestData: { + customer360Type: AsmCustomer360Type.REVIEW_LIST, + }, + config: { pageSize: 5 }, + }, + ], }, - }, - mapsTab: { - googleMaps: { - apiKey: 'AIzaSyAEwnpFNr0duKCE0DClFE7RRJJ9zUmJ8u8', + { + i18nNameKey: 'asm.customer360.mapsTab', + components: [ + { + component: AsmCustomerMapComponent, + requestData: { + customer360Type: AsmCustomer360Type.STORE_LOCATION, + }, + config: { + googleMapsApiKey: 'AIzaSyAEwnpFNr0duKCE0DClFE7RRJJ9zUmJ8u8', + pageSize: 10, + }, + }, + ], }, - pageSize: 10, - }, + ], }, }, }; diff --git a/feature-libs/asm/root/facade/asm.facade.ts b/feature-libs/asm/root/facade/asm.facade.ts new file mode 100644 index 00000000000..85716a2c81b --- /dev/null +++ b/feature-libs/asm/root/facade/asm.facade.ts @@ -0,0 +1,80 @@ +import { Injectable } from '@angular/core'; +import { facadeFactory } from '@spartacus/core'; +import { Observable } from 'rxjs'; +import { ASM_FEATURE } from '../feature-name'; +import { + AsmCustomer360Params, + AsmCustomer360Query, + AsmCustomer360Response, +} from '../model'; +import { + AsmUi, + BindCartParams, + CustomerSearchOptions, + CustomerSearchPage, +} from '../model/asm.models'; + +@Injectable({ + providedIn: 'root', + useFactory: () => + facadeFactory({ + facade: AsmFacade, + feature: ASM_FEATURE, + methods: [ + 'bindCart', + 'customerSearch', + 'customerSearchReset', + 'getCustomerSearchResults', + 'getCustomerSearchResultsLoading', + 'updateAsmUiState', + 'getAsmUiState', + 'fetchCustomer360Data', + 'getCustomer360Data', + ], + }), +}) +export abstract class AsmFacade { + /** + * Bind an anonymous cart to customer + * @param options + */ + abstract bindCart(options: BindCartParams): Observable; + + /** + * Search for customers + * @param options + */ + abstract customerSearch(options: CustomerSearchOptions): void; + + /** + * Reset the customer search result data to the initial state. + */ + abstract customerSearchReset(): void; + + /** + * Returns the customer search result data. + */ + abstract getCustomerSearchResults(): Observable; + + /** + * Returns the customer search result loading status. + */ + abstract getCustomerSearchResultsLoading(): Observable; + + /** + * Updates the state of the ASM UI + */ + abstract updateAsmUiState(asmUi: AsmUi): void; + + /** + * Get the state of the ASM UI + */ + abstract getAsmUiState(): Observable; + + abstract fetchCustomer360Data( + queries: Array, + options: AsmCustomer360Params + ): void; + + abstract getCustomer360Data(): Observable; +} diff --git a/feature-libs/asm/root/model/asm-360.model.ts b/feature-libs/asm/root/model/asm-360.model.ts new file mode 100644 index 00000000000..76651917172 --- /dev/null +++ b/feature-libs/asm/root/model/asm-360.model.ts @@ -0,0 +1,58 @@ +import { UrlCommand, User } from "@spartacus/core"; + +export interface AsmCustomer360Params { + userId: string; +} + +export enum AsmCustomer360Type { + REVIEW_LIST = 'C360ReviewList', + STORE_LOCATION = 'C360StoreLocation', +} + +export interface AdditionalRequestParameters { + timeout?: number; +} + +export interface AsmCustomer360Query { + customer360Type: AsmCustomer360Type; + additionalRequestParameters?: AdditionalRequestParameters; +} + +export interface AsmCustomer360Review { + productName: string; + productCode: string; + createdAt: string; + updatedAt: string; + rating: string; + reviewStatus: string; + reviewText: string; +} + +export interface AsmCustomer360ReviewList { + type: AsmCustomer360Type.REVIEW_LIST; + reviews: Array; +} + +export interface AsmCustomer360StoreLocation { + type: AsmCustomer360Type.STORE_LOCATION; + address: string; +} + +export type AsmCustomer360Data = + | AsmCustomer360ReviewList + | AsmCustomer360StoreLocation; + +export interface AsmCustomer360Response { + value: Array; +} + +export enum AsmDialogActionType { + START_SESSION = 'START_SESSION', + NAVIGATE = 'NAVIGATE', +} + +export interface AsmDialogActionEvent { + selectedUser: User; + actionType: AsmDialogActionType; + route?: UrlCommand; +} diff --git a/feature-libs/asm/root/model/asm.models.ts b/feature-libs/asm/root/model/asm.models.ts deleted file mode 100644 index f007cd7a855..00000000000 --- a/feature-libs/asm/root/model/asm.models.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { PaginationModel, SortModel, UrlCommand, User } from '@spartacus/core'; - -export enum AsmDialogActionType { - START_SESSION = 'START_SESSION', - NAVIGATE = 'NAVIGATE', -} -export interface CustomerSearchPage { - entries: User[]; - pagination?: PaginationModel; - sorts?: SortModel[]; -} - -export interface CustomerSearchOptions { - query?: string; - pageSize?: number; -} - -export interface AsmUi { - collapsed?: boolean; -} - -export interface BindCartParams { - cartId: string; - customerId: string; -} - -export interface Customer360Section { - sectionTitle: CUSTOMER_360_SECTION_TITLE; - sectionContent: string; -} - -export interface AsmDialogActionEvent { - selectedUser: User; - actionType: AsmDialogActionType; - route?: UrlCommand; -} - -export enum CUSTOMER_360_SECTION_TITLE { - OVERVIEW = 'OVERVIEW', - PROFILE = 'PROFILE', - ACTIVITY = 'ACTIVITY', - FEEDBACK = 'FEEDBACK', - PROMOTIONS = 'PROMOTIONS', - MAPS = 'MAPS', -} diff --git a/feature-libs/asm/root/model/customer-360-section-config.ts b/feature-libs/asm/root/model/customer-360-section-config.ts new file mode 100644 index 00000000000..e72e127bf64 --- /dev/null +++ b/feature-libs/asm/root/model/customer-360-section-config.ts @@ -0,0 +1,4 @@ +export abstract class Customer360SectionConfig { + googleMapsApiKey?: string; + pageSize?: number; +} diff --git a/feature-libs/asm/root/model/customer-360-section-data.ts b/feature-libs/asm/root/model/customer-360-section-data.ts new file mode 100644 index 00000000000..95445534193 --- /dev/null +++ b/feature-libs/asm/root/model/customer-360-section-data.ts @@ -0,0 +1,3 @@ +export class Customer360SectionData { + constructor(public data: Data) {} +} diff --git a/feature-libs/asm/root/model/customer-360-tab-config.ts b/feature-libs/asm/root/model/customer-360-tab-config.ts new file mode 100644 index 00000000000..80966d8d1be --- /dev/null +++ b/feature-libs/asm/root/model/customer-360-tab-config.ts @@ -0,0 +1,14 @@ +import { Type } from '@angular/core'; +import { AsmCustomer360Query } from '@spartacus/asm/root'; + +import { Customer360SectionConfig } from './customer-360-section-config'; + +export abstract class AsmCustomer360TabConfig { + components: Array<{ + component: Type; + /** Data that can be associated to a component used to fetch information from a backend. */ + requestData?: AsmCustomer360Query; + config?: Customer360SectionConfig; + }>; + i18nNameKey: string; +} diff --git a/feature-libs/asm/root/model/customer-360-tabs-config.ts b/feature-libs/asm/root/model/customer-360-tabs-config.ts new file mode 100644 index 00000000000..db59f946962 --- /dev/null +++ b/feature-libs/asm/root/model/customer-360-tabs-config.ts @@ -0,0 +1,5 @@ +import { AsmCustomer360TabConfig } from './customer-360-tab-config'; + +export abstract class AsmCustomer360TabsConfig { + tabs?: Array; +} diff --git a/feature-libs/asm/root/model/index.ts b/feature-libs/asm/root/model/index.ts index 2eedbd48cf2..b26c89b7b37 100644 --- a/feature-libs/asm/root/model/index.ts +++ b/feature-libs/asm/root/model/index.ts @@ -7,3 +7,9 @@ export * from './augmented-core.model'; export * from './cart-binding.models'; export * from './customer-list.model'; + +export * from './asm-360.model'; +export * from './customer-360-section-config'; +export * from './customer-360-section-data'; +export * from './customer-360-tab-config'; +export * from './customer-360-tabs-config'; diff --git a/feature-libs/asm/styles/_asm-variables.scss b/feature-libs/asm/styles/_asm-variables.scss index 269d2827c13..3ffabdda3fc 100644 --- a/feature-libs/asm/styles/_asm-variables.scss +++ b/feature-libs/asm/styles/_asm-variables.scss @@ -1,5 +1,5 @@ $asmLinkColor: #2480c5; -$asmVisualFocusColor: #0070F2; +$asmVisualFocusColor: #0070f2; $asmMainTextColor: #14293a; $asmMainSecondaryTextColor: #747881; -$asmTabTextColor: #333333 +$asmTabTextColor: #333333; diff --git a/feature-libs/asm/styles/components/_asm-customer-360.component.scss b/feature-libs/asm/styles/components/_asm-customer-360.component.scss index a106a803dea..ac9cef746b7 100644 --- a/feature-libs/asm/styles/components/_asm-customer-360.component.scss +++ b/feature-libs/asm/styles/components/_asm-customer-360.component.scss @@ -85,6 +85,7 @@ font-weight: var(--cx-font-weight-semi); border-bottom: 1px solid white; padding: 0 1rem 1rem; + text-transform: uppercase; cursor: pointer; &.active { cursor: default; diff --git a/feature-libs/asm/styles/components/_asm-customer-overview.scss b/feature-libs/asm/styles/components/_asm-customer-overview.scss index c1bfc10e153..bf15eab6e53 100644 --- a/feature-libs/asm/styles/components/_asm-customer-overview.scss +++ b/feature-libs/asm/styles/components/_asm-customer-overview.scss @@ -66,7 +66,7 @@ } .cart { &-divider { - border-right: 1px solid #AFAFAF; + border-right: 1px solid #afafaf; height: 20px; } diff --git a/feature-libs/asm/styles/components/_asm-customer-profile.scss b/feature-libs/asm/styles/components/_asm-customer-profile.scss index 0c8482bcbfc..fa09ebed581 100644 --- a/feature-libs/asm/styles/components/_asm-customer-profile.scss +++ b/feature-libs/asm/styles/components/_asm-customer-profile.scss @@ -3,7 +3,7 @@ padding: 0 48px; h4 { - font-weight: var( --cx-font-weight-semi); + font-weight: var(--cx-font-weight-semi); } .cx-asm-profile-address-cell { diff --git a/feature-libs/asm/styles/components/_asm-customer-table.component.scss b/feature-libs/asm/styles/components/_asm-customer-table.component.scss index 2ee60f5617e..33b7f1196a6 100644 --- a/feature-libs/asm/styles/components/_asm-customer-table.component.scss +++ b/feature-libs/asm/styles/components/_asm-customer-table.component.scss @@ -18,6 +18,7 @@ &-text { padding-top: 30px; padding-bottom: 7px; + text-transform: uppercase; } &-pages { @@ -88,6 +89,7 @@ font-size: 13px; color: #7f90a4; cursor: pointer; + text-transform: uppercase; user-select: none; &:hover, @@ -105,12 +107,12 @@ position: relative; } - &.desc::after { + &.asc::after { border-bottom: 5px solid #7f90a4; bottom: 11px; } - &.asc::after { + &.desc::after { border-top: 5px solid #7f90a4; top: 13px; } diff --git a/feature-libs/storefinder/core/facade/store-finder.service.ts b/feature-libs/storefinder/core/facade/store-finder.service.ts index 229db0b0d21..354f8f24cf3 100644 --- a/feature-libs/storefinder/core/facade/store-finder.service.ts +++ b/feature-libs/storefinder/core/facade/store-finder.service.ts @@ -104,7 +104,7 @@ export class StoreFinderService implements OnDestroy { */ findStoresAction( queryText: string, - searchConfig?: SearchConfig, + searchConfig: SearchConfig = {}, longitudeLatitude?: GeoPoint, countryIsoCode?: string, useMyLocation?: boolean, From 35888b14b669f70f1c18e77df67a0b04575c89fb Mon Sep 17 00:00:00 2001 From: Lee Date: Thu, 13 Oct 2022 13:07:56 -0400 Subject: [PATCH 021/175] WIP: Fixing code to make the app build, but the app does not initialize properly: --- .../asm/components/asm-components.module.ts | 6 ------ .../asm-customer-360.component.ts | 19 +++++++++---------- .../asm-customer-activity.component.ts | 2 +- .../asm-customer-map.component.ts | 4 +--- .../asm-customer-product-reviews.component.ts | 4 +--- .../asm-customer-profile.component.ts | 2 -- .../asm-customer-support-tickets.component.ts | 2 +- .../asm/root/config/default-asm-config.ts | 5 +++++ .../_customer-emulation.component.scss | 1 + 9 files changed, 19 insertions(+), 26 deletions(-) diff --git a/feature-libs/asm/components/asm-components.module.ts b/feature-libs/asm/components/asm-components.module.ts index 9d846981ca2..e6e609ac61e 100644 --- a/feature-libs/asm/components/asm-components.module.ts +++ b/feature-libs/asm/components/asm-components.module.ts @@ -28,9 +28,6 @@ import { SpinnerModule, } from '@spartacus/storefront'; import { AsmBindCartComponent } from './asm-bind-cart/asm-bind-cart.component'; -import { AsmCustomerOverviewComponent } from './asm-customer-360/asm-customer-overview/asm-customer-overview.component'; -import { AsmProductItemComponent } from './asm-customer-360/asm-customer-overview/asm-product-item/asm-product-item.component'; -import { AsmCustomerProfileComponent } from './asm-customer-360/asm-customer-profile/asm-customer-profile.component'; import { AsmCustomer360ComponentModule } from './asm-customer-360/asm-customer-360.component.module'; import { AsmMainUiComponent } from './asm-main-ui/asm-main-ui.component'; import { AsmSessionTimerComponent } from './asm-session-timer/asm-session-timer.component'; @@ -77,9 +74,6 @@ import { DotSpinnerComponent } from './dot-spinner/dot-spinner.component'; AsmToggleUiComponent, AsmBindCartComponent, DotSpinnerComponent, - AsmCustomerProfileComponent, - AsmCustomerOverviewComponent, - AsmProductItemComponent ], exports: [ AsmMainUiComponent, diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.ts index b840f18beaa..0f17bea63ae 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.ts @@ -6,25 +6,24 @@ import { OnInit, StaticProvider, } from '@angular/core'; +import { AsmService } from '@spartacus/asm/core'; import { + AsmConfig, AsmCustomer360Data, AsmCustomer360Query, + AsmCustomer360TabConfig, AsmDialogActionEvent, AsmDialogActionType, - AsmFacade, + Customer360SectionConfig, + Customer360SectionData, } from '@spartacus/asm/root'; import { UrlCommand, User } from '@spartacus/core'; import { ICON_TYPE } from '@spartacus/storefront'; -import { ModalService } from '@spartacus/storefront'; import { take } from 'rxjs/operators'; -import { AsmConfig } from 'feature-libs/asm/core'; -import { AsmCustomer360TabConfig } from 'feature-libs/asm/core/models/customer-360-tab-config'; -import { Customer360SectionConfig } from 'feature-libs/asm/core/models/customer-360-section-config'; import { getAsmDialogActionEvent } from '../../core/utils/utils'; import { Customer360SectionContextSource } from './sections/customer-360-section-context-source.model'; import { Customer360SectionContext } from './sections/customer-360-section-context.model'; -import { Customer360SectionData } from 'feature-libs/asm/core/models/customer-360-section-data'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -48,9 +47,8 @@ export class AsmCustomer360Component implements OnInit { constructor( asmConfig: AsmConfig, - protected asmService: AsmFacade, - protected injector: Injector, - protected modalService: ModalService + protected asmService: AsmService, + protected injector: Injector ) { this.tabs = asmConfig.asm?.customer360?.tabs ?? []; this.currentTab = this.tabs[0]; @@ -126,7 +124,8 @@ export class AsmCustomer360Component implements OnInit { } closeModal(reason?: any): void { - this.modalService.closeActiveModal(reason); + reason; + // this.modalService.closeActiveModal(reason); } createInjector(config: unknown, sectionData?: unknown): Injector { diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.component.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.component.ts index 2457d55b955..dc748d25bf6 100644 --- a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.component.ts +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-activity/asm-customer-activity.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Customer360SectionConfig } from 'feature-libs/asm/core/models/customer-360-section-config'; +import { Customer360SectionConfig } from '@spartacus/asm/root'; import { formatEpochTime } from '../../asm-customer-360.utils'; import { CustomerTableColumn } from '../../asm-customer-ui-components/asm-customer-table/asm-customer-table.model'; diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.ts index 3eaf50126f6..b1971cbe7f6 100644 --- a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.ts +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-map/asm-customer-map.component.ts @@ -6,14 +6,12 @@ import { OnInit, } from '@angular/core'; import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; -import { AsmCustomer360StoreLocation } from '@spartacus/asm/root'; +import { AsmCustomer360StoreLocation, Customer360SectionConfig, Customer360SectionData } from '@spartacus/asm/root'; import { PointOfService } from '@spartacus/core'; import { StoreFinderSearchPage, StoreFinderService, } from '@spartacus/storefinder/core'; -import { Customer360SectionConfig } from 'feature-libs/asm/core/models/customer-360-section-config'; -import { Customer360SectionData } from 'feature-libs/asm/core/models/customer-360-section-data'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.ts index 58b1956d8bf..a58ac279334 100644 --- a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.ts +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-product-reviews/asm-customer-product-reviews.component.ts @@ -1,8 +1,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Customer360SectionConfig } from '@spartacus/asm/core'; -import { AsmCustomer360ReviewList } from '@spartacus/asm/root'; +import { AsmCustomer360ReviewList, Customer360SectionConfig, Customer360SectionData } from '@spartacus/asm/root'; import { SemanticPathService } from '@spartacus/core'; -import { Customer360SectionData } from 'feature-libs/asm/core/models/customer-360-section-data'; import { combineStrings, formatEpochTime } from '../../asm-customer-360.utils'; import { CustomerTableColumn } from '../../asm-customer-ui-components/asm-customer-table/asm-customer-table.model'; diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.component.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.component.ts index ddc8c6b3618..b381d1db1e2 100644 --- a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.component.ts +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-profile/asm-customer-profile.component.ts @@ -8,7 +8,6 @@ import { Card, FocusConfig, ICON_TYPE, - ModalService, } from '@spartacus/storefront'; import { combineLatest, forkJoin, of, Observable, Subscription } from 'rxjs'; @@ -40,7 +39,6 @@ export class AsmCustomerProfileComponent implements OnInit { protected subscription = new Subscription(); constructor( - protected modalService: ModalService, protected breakpointService: BreakpointService, protected asmConfig: AsmConfig, protected translation: TranslationService diff --git a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.ts b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.ts index 6e42c2177fd..8bcc8db3f0b 100644 --- a/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.ts +++ b/feature-libs/asm/components/asm-customer-360/sections/asm-customer-support-tickets/asm-customer-support-tickets.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Customer360SectionConfig } from '@spartacus/asm/core'; +import { Customer360SectionConfig } from '@spartacus/asm/root'; import { formatEpochTime, replaceTokens } from '../../asm-customer-360.utils'; import { CustomerTableColumn } from '../../asm-customer-ui-components/asm-customer-table/asm-customer-table.model'; diff --git a/feature-libs/asm/root/config/default-asm-config.ts b/feature-libs/asm/root/config/default-asm-config.ts index 79d39aeb156..586eac836f9 100644 --- a/feature-libs/asm/root/config/default-asm-config.ts +++ b/feature-libs/asm/root/config/default-asm-config.ts @@ -5,6 +5,11 @@ */ import { ICON_TYPE } from '@spartacus/storefront'; + +// TODO: Cannot import from components module. Need to refactor +import { AsmCustomerMapComponent, AsmCustomerOverviewComponent, AsmCustomerProductReviewsComponent } from 'feature-libs/asm/components/asm-customer-360/sections/components'; + +import { AsmCustomer360Type } from '../model'; import { CustomerListColumnActionType } from '../model/customer-list.model'; import { AsmConfig } from './asm-config'; diff --git a/feature-libs/asm/styles/components/_customer-emulation.component.scss b/feature-libs/asm/styles/components/_customer-emulation.component.scss index 2dc6f36465c..2d28a8dc6e2 100644 --- a/feature-libs/asm/styles/components/_customer-emulation.component.scss +++ b/feature-libs/asm/styles/components/_customer-emulation.component.scss @@ -47,6 +47,7 @@ fill: #fff; } } + } @include forVersion(5.1) { color: #aa0808; From 4a650334351545ef007360e08dc76436612c687e Mon Sep 17 00:00:00 2001 From: Lee Date: Fri, 14 Oct 2022 10:06:32 -0400 Subject: [PATCH 022/175] Reverting accidentally changed code from rebase resolve --- .../components/asm-main-ui/asm-main-ui.component.ts | 10 ++++++++-- .../customer-emulation.component.html | 6 +----- feature-libs/asm/root/config/default-asm-config.ts | 2 +- feature-libs/asm/root/model/customer-360-tab-config.ts | 2 +- .../components/_customer-emulation.component.scss | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/feature-libs/asm/components/asm-main-ui/asm-main-ui.component.ts b/feature-libs/asm/components/asm-main-ui/asm-main-ui.component.ts index 7b5c9722f57..5cb78bd87ac 100644 --- a/feature-libs/asm/components/asm-main-ui/asm-main-ui.component.ts +++ b/feature-libs/asm/components/asm-main-ui/asm-main-ui.component.ts @@ -107,6 +107,7 @@ export class AsmMainUiComponent implements OnInit, OnDestroy { } }) ); + this.csAgentTokenLoading$ = this.csAgentAuthService.getCustomerSupportAgentTokenLoading(); this.customer$ = this.authService.isUserLoggedIn().pipe( @@ -171,8 +172,13 @@ export class AsmMainUiComponent implements OnInit, OnDestroy { startCustomerEmulationSession({ customerId }: { customerId?: string }): void { if (customerId) { - this.startingCustomerSession = - this.asmComponentService.startCustomerEmulationSession(customerId); + this.csAgentAuthService.startCustomerEmulationSession(customerId); + this.startingCustomerSession = true; + } else { + this.globalMessageService.add( + { key: 'asm.error.noCustomerId' }, + GlobalMessageType.MSG_TYPE_ERROR + ); } } diff --git a/feature-libs/asm/components/customer-emulation/customer-emulation.component.html b/feature-libs/asm/components/customer-emulation/customer-emulation.component.html index 902578bc2df..5c7d87a693c 100644 --- a/feature-libs/asm/components/customer-emulation/customer-emulation.component.html +++ b/feature-libs/asm/components/customer-emulation/customer-emulation.component.html @@ -15,11 +15,7 @@ - diff --git a/feature-libs/asm/root/config/default-asm-config.ts b/feature-libs/asm/root/config/default-asm-config.ts index 586eac836f9..529f1529baa 100644 --- a/feature-libs/asm/root/config/default-asm-config.ts +++ b/feature-libs/asm/root/config/default-asm-config.ts @@ -8,8 +8,8 @@ import { ICON_TYPE } from '@spartacus/storefront'; // TODO: Cannot import from components module. Need to refactor import { AsmCustomerMapComponent, AsmCustomerOverviewComponent, AsmCustomerProductReviewsComponent } from 'feature-libs/asm/components/asm-customer-360/sections/components'; +import { AsmCustomer360Type } from '../model/asm-360.model'; -import { AsmCustomer360Type } from '../model'; import { CustomerListColumnActionType } from '../model/customer-list.model'; import { AsmConfig } from './asm-config'; diff --git a/feature-libs/asm/root/model/customer-360-tab-config.ts b/feature-libs/asm/root/model/customer-360-tab-config.ts index 80966d8d1be..f25140c7f20 100644 --- a/feature-libs/asm/root/model/customer-360-tab-config.ts +++ b/feature-libs/asm/root/model/customer-360-tab-config.ts @@ -1,6 +1,6 @@ import { Type } from '@angular/core'; -import { AsmCustomer360Query } from '@spartacus/asm/root'; +import { AsmCustomer360Query } from './asm-360.model'; import { Customer360SectionConfig } from './customer-360-section-config'; export abstract class AsmCustomer360TabConfig { diff --git a/feature-libs/asm/styles/components/_customer-emulation.component.scss b/feature-libs/asm/styles/components/_customer-emulation.component.scss index 2d28a8dc6e2..60ea970cab9 100644 --- a/feature-libs/asm/styles/components/_customer-emulation.component.scss +++ b/feature-libs/asm/styles/components/_customer-emulation.component.scss @@ -47,7 +47,6 @@ fill: #fff; } } - } @include forVersion(5.1) { color: #aa0808; @@ -72,6 +71,7 @@ background-color: #ffd6ea; border: 1px solid #ffd6ea; margin-left: 15px; + } } @include forVersion(5.1) { From 6498ba2a889dab9507dc9e0a12cbedb7635e2ca4 Mon Sep 17 00:00:00 2001 From: Lee Date: Fri, 14 Oct 2022 11:13:06 -0400 Subject: [PATCH 023/175] WIP: Customer 360 uses LaunchDialogService --- .../asm/components/asm-components.module.ts | 2 + .../asm-customer-360.component.html | 2 +- .../asm-customer-360.component.ts | 90 ++++++++++--------- .../default-customer-360-layout.config.ts | 18 ++++ .../customer-emulation.component.html | 2 +- .../customer-emulation.component.ts | 40 ++++----- .../asm/root/config/default-asm-config.ts | 9 +- feature-libs/asm/root/model/asm-360.model.ts | 8 ++ 8 files changed, 103 insertions(+), 68 deletions(-) create mode 100644 feature-libs/asm/components/asm-customer-360/default-customer-360-layout.config.ts diff --git a/feature-libs/asm/components/asm-components.module.ts b/feature-libs/asm/components/asm-components.module.ts index e6e609ac61e..706397a0a84 100644 --- a/feature-libs/asm/components/asm-components.module.ts +++ b/feature-libs/asm/components/asm-components.module.ts @@ -29,6 +29,7 @@ import { } from '@spartacus/storefront'; import { AsmBindCartComponent } from './asm-bind-cart/asm-bind-cart.component'; import { AsmCustomer360ComponentModule } from './asm-customer-360/asm-customer-360.component.module'; +import { defaultCustomer360LayoutConfig } from './asm-customer-360/default-customer-360-layout.config'; import { AsmMainUiComponent } from './asm-main-ui/asm-main-ui.component'; import { AsmSessionTimerComponent } from './asm-session-timer/asm-session-timer.component'; import { FormatTimerPipe } from './asm-session-timer/format-timer.pipe'; @@ -91,6 +92,7 @@ import { DotSpinnerComponent } from './dot-spinner/dot-spinner.component'; providers: [ provideConfig(defaultAsmLayoutConfig), provideConfig(defaultCustomerListLayoutConfig), + provideConfig(defaultCustomer360LayoutConfig), ], }) export class AsmComponentsModule {} diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html index 08b0e772177..b9839870e18 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html @@ -52,7 +52,7 @@
diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.module.ts b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.module.ts index 59923f24b77..2a03403a38a 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.module.ts +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.module.ts @@ -9,11 +9,13 @@ import { AsmCustomerProfileModule } from './sections/asm-customer-profile/asm-cu import { AsmCustomerPromotionsModule } from './sections/asm-customer-promotions/asm-customer-promotions.module'; import { AsmCustomerSupportTicketsComponentModule } from './sections/asm-customer-support-tickets/asm-customer-support-tickets.component.module'; import { AsmCustomerProductReviewsComponentModule } from './sections/asm-customer-product-reviews/asm-customer-product-reviews.component.module'; +import { PageComponentModule } from '@spartacus/storefront'; @NgModule({ imports: [ CommonModule, I18nModule, + PageComponentModule, AsmCustomerOverviewModule, AsmCustomerProfileModule, AsmCustomerActivityModule, diff --git a/feature-libs/asm/components/asm-customer-360/index.ts b/feature-libs/asm/components/asm-customer-360/index.ts new file mode 100644 index 00000000000..4774fa48cce --- /dev/null +++ b/feature-libs/asm/components/asm-customer-360/index.ts @@ -0,0 +1 @@ +export * from './sections/components'; diff --git a/feature-libs/asm/components/public_api.ts b/feature-libs/asm/components/public_api.ts index 0b858d6e8ef..547dab8c307 100644 --- a/feature-libs/asm/components/public_api.ts +++ b/feature-libs/asm/components/public_api.ts @@ -16,3 +16,4 @@ export * from './customer-list/customer-list.component'; export * from './customer-selection/customer-selection.component'; export * from './dot-spinner/dot-spinner.component'; export * from './services/index'; +export * from './asm-customer-360/index'; diff --git a/feature-libs/asm/root/asm-root.module.ts b/feature-libs/asm/root/asm-root.module.ts index df257184520..625cff6217d 100644 --- a/feature-libs/asm/root/asm-root.module.ts +++ b/feature-libs/asm/root/asm-root.module.ts @@ -14,6 +14,7 @@ import { } from '@spartacus/core'; import { AsmLoaderModule } from './asm-loader.module'; import { defaultAsmConfig } from './config/default-asm-config'; +import { ASM_FEATURE } from './feature-name'; import { UserIdHttpHeaderInterceptor } from './interceptors/user-id-http-header.interceptor'; import { AsmAuthHttpHeaderService } from './services/asm-auth-http-header.service'; import { AsmAuthStorageService } from './services/asm-auth-storage.service'; @@ -23,6 +24,16 @@ import { AsmAuthService } from './services/asm-auth.service'; imports: [AsmLoaderModule], providers: [ provideDefaultConfig(defaultAsmConfig), + provideDefaultConfig({ + featureModules: { + [ASM_FEATURE]: { + cmsComponents: [ + 'AsmCustomer360OverviewComponent', + 'AsmCustomer360ProfileComponent' + ], + }, + } + }), { provide: AuthStorageService, useExisting: AsmAuthStorageService, diff --git a/feature-libs/asm/root/config/default-asm-config.ts b/feature-libs/asm/root/config/default-asm-config.ts index 8a2e6975b29..f284bc12a45 100644 --- a/feature-libs/asm/root/config/default-asm-config.ts +++ b/feature-libs/asm/root/config/default-asm-config.ts @@ -55,7 +55,24 @@ export const defaultAsmConfig: AsmConfig = { ], }, customer360: { - tabs: [], + tabs: [ + { + i18nNameKey: 'asm.customer360.overviewTab', + components: [ + { + component: 'AsmCustomer360OverviewComponent', + }, + ], + }, + { + i18nNameKey: 'asm.customer360.profileTab', + components: [ + { + component: 'AsmCustomer360ProfileComponent', + }, + ], + }, + ], }, /* customer360: { diff --git a/feature-libs/asm/root/model/customer-360-tab-config.ts b/feature-libs/asm/root/model/customer-360-tab-config.ts index f25140c7f20..bcdb0873926 100644 --- a/feature-libs/asm/root/model/customer-360-tab-config.ts +++ b/feature-libs/asm/root/model/customer-360-tab-config.ts @@ -1,11 +1,9 @@ -import { Type } from '@angular/core'; - import { AsmCustomer360Query } from './asm-360.model'; import { Customer360SectionConfig } from './customer-360-section-config'; export abstract class AsmCustomer360TabConfig { components: Array<{ - component: Type; + component: string; /** Data that can be associated to a component used to fetch information from a backend. */ requestData?: AsmCustomer360Query; config?: Customer360SectionConfig; From f6e73970f64f845991d4a657184958c2283f57d4 Mon Sep 17 00:00:00 2001 From: Hakwoo Kim Date: Fri, 14 Oct 2022 13:39:02 -0400 Subject: [PATCH 025/175] fix dialog style --- .../asm-customer-360.component.html | 132 +++++++++--------- .../asm-customer-360.component.module.ts | 4 +- .../asm-customer-360.component.ts | 3 +- 3 files changed, 72 insertions(+), 67 deletions(-) diff --git a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html index 883e6eac42a..807211bdb48 100644 --- a/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html +++ b/feature-libs/asm/components/asm-customer-360/asm-customer-360.component.html @@ -1,78 +1,83 @@ -
+
- -