diff --git a/feature-libs/customer-ticketing/assets/translations/en/index.ts b/feature-libs/customer-ticketing/assets/translations/en/index.ts index ca65de6629e..1ceb1689e04 100644 --- a/feature-libs/customer-ticketing/assets/translations/en/index.ts +++ b/feature-libs/customer-ticketing/assets/translations/en/index.ts @@ -5,7 +5,9 @@ */ import { customerTicketing } from './customer-ticketing'; +import { myAccountV2CustomerTicketing } from './my-account-v2-customer-ticketing'; export const en = { customerTicketing, + myAccountV2CustomerTicketing, }; diff --git a/feature-libs/customer-ticketing/assets/translations/en/my-account-v2-customer-ticketing.ts b/feature-libs/customer-ticketing/assets/translations/en/my-account-v2-customer-ticketing.ts new file mode 100644 index 00000000000..ef58477e5fc --- /dev/null +++ b/feature-libs/customer-ticketing/assets/translations/en/my-account-v2-customer-ticketing.ts @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export const myAccountV2CustomerTicketing = { + myAccountV2CustomerTicketing: { + heading: 'Customer Service', + showMore: 'Show More', + changedOn: 'Changed On: {{value}}', + ticketId: 'ID: {{value}}', + subjectLabel: 'Subject', + idLabel: 'Customer Service Id', + }, +}; diff --git a/feature-libs/customer-ticketing/assets/translations/translations.ts b/feature-libs/customer-ticketing/assets/translations/translations.ts index 42919df7129..2edbb9e218f 100644 --- a/feature-libs/customer-ticketing/assets/translations/translations.ts +++ b/feature-libs/customer-ticketing/assets/translations/translations.ts @@ -19,4 +19,5 @@ export const customerTicketingTranslationChunksConfig: TranslationChunksConfig = 'createCustomerTicket', 'customerTicketingDetails', ], + myAccountV2CustomerTicketing: ['myAccountV2CustomerTicketing'], }; diff --git a/feature-libs/customer-ticketing/components/customer-ticketing-components.module.ts b/feature-libs/customer-ticketing/components/customer-ticketing-components.module.ts index e70c6747ee6..b0cb20fee22 100644 --- a/feature-libs/customer-ticketing/components/customer-ticketing-components.module.ts +++ b/feature-libs/customer-ticketing/components/customer-ticketing-components.module.ts @@ -15,6 +15,7 @@ import { CustomerTicketingListModule } from './list/customer-ticketing-list'; import { CustomerTicketingDetailsModule } from './details/customer-ticketing-details'; import { CustomerTicketingReopenModule } from './details/customer-ticketing-reopen'; import { CustomerTicketingMessagesModule } from './details/customer-ticketing-messages'; +import { MyAccountV2CustomerTicketingModule } from './my-account-v2'; @NgModule({ imports: [ @@ -26,6 +27,7 @@ import { CustomerTicketingMessagesModule } from './details/customer-ticketing-me CustomerTicketingListModule, CustomerTicketingMessagesModule, CustomerTicketingCreateModule, + MyAccountV2CustomerTicketingModule, ], providers: [provideDefaultConfig(defaultCustomerTicketingFormLayoutConfig)], }) diff --git a/feature-libs/customer-ticketing/components/my-account-v2/index.ts b/feature-libs/customer-ticketing/components/my-account-v2/index.ts new file mode 100644 index 00000000000..6e9653222fa --- /dev/null +++ b/feature-libs/customer-ticketing/components/my-account-v2/index.ts @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './my-account-v2-customer-ticketing.component'; +export * from './my-account-v2-customer-ticketing.module'; diff --git a/feature-libs/customer-ticketing/components/my-account-v2/my-account-v2-customer-ticketing.component.html b/feature-libs/customer-ticketing/components/my-account-v2/my-account-v2-customer-ticketing.component.html new file mode 100644 index 00000000000..dcb4bf5dba5 --- /dev/null +++ b/feature-libs/customer-ticketing/components/my-account-v2/my-account-v2-customer-ticketing.component.html @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + diff --git a/feature-libs/customer-ticketing/components/my-account-v2/my-account-v2-customer-ticketing.component.spec.ts b/feature-libs/customer-ticketing/components/my-account-v2/my-account-v2-customer-ticketing.component.spec.ts new file mode 100644 index 00000000000..17d6b181743 --- /dev/null +++ b/feature-libs/customer-ticketing/components/my-account-v2/my-account-v2-customer-ticketing.component.spec.ts @@ -0,0 +1,131 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { RouterTestingModule } from '@angular/router/testing'; +import { + I18nTestingModule, + RoutingService, + TranslationService, +} from '@spartacus/core'; +import { TicketList } from '@spartacus/customer-ticketing/root'; +import { BehaviorSubject, EMPTY, Observable, of } from 'rxjs'; +import { MyAccountV2CustomerTicketingComponent } from './my-account-v2-customer-ticketing.component'; + +const mockTicketList: TicketList = { + pagination: {}, + sorts: [], + tickets: [ + { + id: '0000001', + modifiedAt: '2021-01-13T10:06:57+0000', + subject: 'My drill is broken.', + ticketCategory: { + id: 'ENQUIRY', + name: 'Enquiry', + }, + }, + ], +}; + +@Pipe({ + name: 'cxUrl', +}) +class MockUrlPipe implements PipeTransform { + transform() {} +} +@Pipe({ + name: 'cxTranslate', +}) +class MockTranslatePipe implements PipeTransform { + transform(): any {} +} +class MockRoutingService { + go() {} +} + +class MockTranslationService { + translate(): Observable { + return EMPTY; + } +} + +class MockcustomerTicketingFacade { + getTickets( + _pageSize: number, + _currentPage?: number, + _sort?: string + ): Observable { + return mockTicketList$.asObservable(); + } + + clearTicketList() {} +} + +const mockTicketList$ = new BehaviorSubject(mockTicketList); + +describe('MyAccountV2CustomerTicketingComponent', () => { + let component: MyAccountV2CustomerTicketingComponent; + let fixture: ComponentFixture; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule, I18nTestingModule], + declarations: [ + MyAccountV2CustomerTicketingComponent, + MockTranslatePipe, + MockUrlPipe, + ], + providers: [ + { + provide: 'CustomerTicketingFacade', + useClass: MockcustomerTicketingFacade, + }, + { provide: RoutingService, useClass: MockRoutingService }, + { provide: TranslationService, useClass: MockTranslationService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(MyAccountV2CustomerTicketingComponent); + component = fixture.componentInstance; + component.tickets$ = of(mockTicketList); + fixture.detectChanges(); + }) + ); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); + + it('should display heading', () => { + const heading = fixture.debugElement.query( + By.css('.cx-my-account-customer-ticket-heading') + ); + expect(heading.nativeElement).not.toBeNull(); + const link = fixture.debugElement.query(By.css('#show-more-requests')); + expect(link).not.toBeNull(); + }); + + it('should show 1 return request', () => { + const details = fixture.debugElement.query( + By.css('.cx-my-account-customer-ticket-details') + ); + expect(details.nativeElement).not.toBeNull(); + const noTicket = fixture.debugElement.query( + By.css('.cx-my-account-no-ticket') + ); + expect(noTicket).toBeNull(); + }); + it('should show no return request', () => { + component.tickets$ = of({ tickets: [] }); + fixture.detectChanges(); + const details = fixture.debugElement.query( + By.css('.cx-my-account-customer-ticket-details') + ); + expect(details).toBeNull(); + const noTicket = fixture.debugElement.query( + By.css('.cx-my-account-no-ticket') + ); + expect(noTicket).not.toBeNull(); + }); +}); diff --git a/feature-libs/customer-ticketing/components/my-account-v2/my-account-v2-customer-ticketing.component.ts b/feature-libs/customer-ticketing/components/my-account-v2/my-account-v2-customer-ticketing.component.ts new file mode 100644 index 00000000000..86b32be9c45 --- /dev/null +++ b/feature-libs/customer-ticketing/components/my-account-v2/my-account-v2-customer-ticketing.component.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Component, inject } from '@angular/core'; +import { + CustomerTicketingFacade, + TicketList, +} from '@spartacus/customer-ticketing/root'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'cx-my-account-v2-customer-ticketing', + templateUrl: './my-account-v2-customer-ticketing.component.html', +}) +export class MyAccountV2CustomerTicketingComponent { + protected readonly PAGE_SIZE = 1; + protected customerTicketingFacade = inject(CustomerTicketingFacade); + tickets$: Observable = + this.customerTicketingFacade.getTickets(this.PAGE_SIZE); +} diff --git a/feature-libs/customer-ticketing/components/my-account-v2/my-account-v2-customer-ticketing.module.ts b/feature-libs/customer-ticketing/components/my-account-v2/my-account-v2-customer-ticketing.module.ts new file mode 100644 index 00000000000..d1377c5506d --- /dev/null +++ b/feature-libs/customer-ticketing/components/my-account-v2/my-account-v2-customer-ticketing.module.ts @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { NgModule } from '@angular/core'; +import { + provideDefaultConfig, + CmsConfig, + AuthGuard, + I18nModule, + UrlModule, +} from '@spartacus/core'; +import { MyAccountV2CustomerTicketingComponent } from './my-account-v2-customer-ticketing.component'; +import { SpinnerModule } from '@spartacus/storefront'; +import { RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + declarations: [MyAccountV2CustomerTicketingComponent], + exports: [MyAccountV2CustomerTicketingComponent], + providers: [ + provideDefaultConfig({ + cmsComponents: { + MyAccountViewRequestsComponent: { + component: MyAccountV2CustomerTicketingComponent, + guards: [AuthGuard], + }, + }, + }), + ], + imports: [CommonModule, I18nModule, UrlModule, SpinnerModule, RouterModule], +}) +export class MyAccountV2CustomerTicketingModule {} diff --git a/feature-libs/customer-ticketing/components/public_api.ts b/feature-libs/customer-ticketing/components/public_api.ts index 45011b259ac..4ec9befb20d 100644 --- a/feature-libs/customer-ticketing/components/public_api.ts +++ b/feature-libs/customer-ticketing/components/public_api.ts @@ -8,3 +8,4 @@ export * from './customer-ticketing-components.module'; export * from './details/index'; export * from './list/index'; export * from './shared/index'; +export * from './my-account-v2/index'; diff --git a/feature-libs/customer-ticketing/root/customer-ticketing-root.module.ts b/feature-libs/customer-ticketing/root/customer-ticketing-root.module.ts index 57fc9423baf..7900320e7db 100644 --- a/feature-libs/customer-ticketing/root/customer-ticketing-root.module.ts +++ b/feature-libs/customer-ticketing/root/customer-ticketing-root.module.ts @@ -31,6 +31,7 @@ export function defaultCustomerTicketingComponentsConfig(): CmsConfig { 'SupportTicketDetailsComponent', 'SupportTicketReopenComponent', 'SupportTicketCloseComponent', + 'MyAccountViewRequestsComponent', ], }, diff --git a/feature-libs/customer-ticketing/styles/components/_index.scss b/feature-libs/customer-ticketing/styles/components/_index.scss index 8eee9eef57d..180e0aeddcc 100644 --- a/feature-libs/customer-ticketing/styles/components/_index.scss +++ b/feature-libs/customer-ticketing/styles/components/_index.scss @@ -2,12 +2,13 @@ @import './customer-ticketing-details'; @import './customer-ticketing-dialog'; @import './customer-ticketing-list'; +@import './my-account-v2-customer-ticketing'; $customer-ticketing-allowlist: cx-customer-ticketing-create-dialog, cx-customer-ticketing-reopen-dialog, cx-customer-ticketing-close-dialog, cx-customer-ticketing-reopen, cx-customer-ticketing-close, cx-customer-ticketing-create, cx-customer-ticketing-details, - cx-customer-ticketing-list !default; + cx-customer-ticketing-list, cx-my-account-v2-customer-ticketing !default; $skipComponentStyles: () !default; diff --git a/feature-libs/customer-ticketing/styles/components/_my-account-v2-customer-ticketing.scss b/feature-libs/customer-ticketing/styles/components/_my-account-v2-customer-ticketing.scss new file mode 100644 index 00000000000..6bce426d92e --- /dev/null +++ b/feature-libs/customer-ticketing/styles/components/_my-account-v2-customer-ticketing.scss @@ -0,0 +1,45 @@ +%cx-my-account-v2-customer-ticketing { + margin: 1rem 0rem; + border: 1px solid var(--cx-color-medium); + .cx-my-account-customer-ticket-header { + display: flex; + justify-content: space-between; + margin: 1rem; //top-right-bottom-left + .cx-my-account-customer-ticket-heading { + font-size: 18px; + font-weight: 700; + } + .cx-my-account-customer-ticket-show-more { + font-size: 16px; + font-weight: 600; + } + } + .cx-my-account-customer-ticket-body { + display: flex; + justify-content: space-between; + + margin: 1rem 1rem 1rem 1rem; //top-right-bottom-left + padding: 15px 30px; + background-color: var(--cx-color-light); + border: 1px solid var(--cx-color-medium); + border-radius: 0; + .cx-my-account-customer-ticket-details { + display: flex; + justify-content: space-between; + align-items: center; + } + .cx-my-account-customer-ticket-subject { + font-size: 18px; + font-weight: 700; + } + .cx-my-account-customer-ticket-details-light { + font-size: 14px; + font-weight: 400; + color: var(--cx-color-secondary); + } + } + .cx-my-account-no-ticket { + margin: 1rem; + padding: 15px; + } +} diff --git a/feature-libs/order/assets/translations/en/index.ts b/feature-libs/order/assets/translations/en/index.ts index 1936addade9..9452ede269f 100644 --- a/feature-libs/order/assets/translations/en/index.ts +++ b/feature-libs/order/assets/translations/en/index.ts @@ -4,9 +4,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { MyAccountV2Order, order } from './order.i18n'; +import { myAccountV2Order } from './my-account-v2-order.i18n'; +import { order } from './order.i18n'; export const en = { order, - MyAccountV2Order, + myAccountV2Order, }; diff --git a/feature-libs/order/assets/translations/en/my-account-v2-order.i18n.ts b/feature-libs/order/assets/translations/en/my-account-v2-order.i18n.ts new file mode 100644 index 00000000000..cef74941883 --- /dev/null +++ b/feature-libs/order/assets/translations/en/my-account-v2-order.i18n.ts @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export const myAccountV2Order = { + myAccountV2OrderHistory: { + heading: 'All Orders ({{param}})', + item: '{{param}} Item', + items: '{{param}} Items', + totalPrice: 'Total Price: {{param}}', + consignmentCode: 'Consignment {{param}}', + statusDate: 'Last Updated: {{param}}', + returnProcessed: 'Return Processed: {{param}}', + deliveryPointOfServiceDetails: { + itemsToBePickUp: 'To Be Picked Up - ', + }, + checkoutMode: { + deliveryEntries: 'To Be Shipped - ', + }, + checkoutPickupInStore: { + heading: 'To Be Picked Up - ', + }, + orderListResults: 'Orders List', + orderListPagination: 'Orders List pagination', + totalPriceLabel: 'Total Price', + orderPlaced: 'Order Placed On', + orderCodeLabel: 'Order Code', + consignmentDetailLabel: 'Consignment Information', + consignmentNumberLabel: 'Consignment Number', + consignmentStatusLabel: 'Consignment Status', + consignmentStatusDateLabel: 'Last Updated On', + estimateDeliveryLabel: 'Estimated Delivery Date', + }, + myAccountV2OrderDetails: { + returnItems: 'Return Items', + cancelItems: 'Cancel Items', + downloadInvoices: 'Download Invoices', + viewAllOrders: 'View All Orders', + noInvoices: 'Invoices are not generated yet. Please come back later.', + }, + myAccountV2Orders: { + item: '{{value}} Item', + items: '{{value}} Items', + heading: 'Orders And Returns', + orderNumber: 'Order Number ({{value}})', + purchasedOn: 'Purchased On: {{value}}', + orderedItems: 'Ordered Items: {{value}}', + totalPrice: 'Total Price: {{value}}', + orderDetails: 'Order Details', + orderDetailsLabel: 'Show Order Details', + orderStatusLabel: 'Order Status', + returnOrder: 'Return Order', + showMore: 'Show More', + }, +}; diff --git a/feature-libs/order/assets/translations/translations.ts b/feature-libs/order/assets/translations/translations.ts index 335f97ea691..b6e0f65853d 100644 --- a/feature-libs/order/assets/translations/translations.ts +++ b/feature-libs/order/assets/translations/translations.ts @@ -20,5 +20,9 @@ export const orderTranslationChunksConfig: TranslationChunksConfig = { 'returnRequest', 'reorder', ], - MyAccountV2Order: ['myAccountV2OrderHistory', 'myAccountV2OrderDetails'], + myAccountV2Order: [ + 'myAccountV2OrderHistory', + 'myAccountV2OrderDetails', + 'myAccountV2Orders', + ], }; diff --git a/feature-libs/order/components/my-account-v2/index.ts b/feature-libs/order/components/my-account-v2/index.ts new file mode 100644 index 00000000000..061feb454d6 --- /dev/null +++ b/feature-libs/order/components/my-account-v2/index.ts @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './my-account-v2-orders.component'; +export * from './my-account-v2-orders.module'; diff --git a/feature-libs/order/components/my-account-v2/my-account-v2-orders.component.html b/feature-libs/order/components/my-account-v2/my-account-v2-orders.component.html new file mode 100644 index 00000000000..54512613336 --- /dev/null +++ b/feature-libs/order/components/my-account-v2/my-account-v2-orders.component.html @@ -0,0 +1,173 @@ + + +
+ + + + +
+ + + + + + + +
+ +
+
diff --git a/feature-libs/order/components/my-account-v2/my-account-v2-orders.component.spec.ts b/feature-libs/order/components/my-account-v2/my-account-v2-orders.component.spec.ts new file mode 100644 index 00000000000..0dfd82c3071 --- /dev/null +++ b/feature-libs/order/components/my-account-v2/my-account-v2-orders.component.spec.ts @@ -0,0 +1,182 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { RouterTestingModule } from '@angular/router/testing'; +import { + FeaturesConfigModule, + I18nTestingModule, + RoutingService, + TranslationService, +} from '@spartacus/core'; +import { MyAccountV2OrderHistoryService } from '@spartacus/order/core'; +import { OrderHistoryListView } from '@spartacus/order/root'; +import { EMPTY, Observable, of } from 'rxjs'; +import { MyAccountV2OrdersComponent } from './my-account-v2-orders.component'; + +const mockOrders: OrderHistoryListView = { + orders: [ + { + code: '1', + placed: new Date('2018-01-01'), + statusDisplay: 'test', + total: { formattedValue: '1' }, + entries: [ + { product: { name: 'a1' } }, + { product: { name: 'b1', images: {} } }, + ], + }, + { + code: '2', + placed: new Date('2018-01-02'), + statusDisplay: 'test2', + total: { formattedValue: '2' }, + entries: [{ product: { name: 'a1' } }, { product: { name: 'b1' } }], + }, + ], + pagination: { totalResults: 1, totalPages: 2, sort: 'byDate' }, + sorts: [{ code: 'byDate', selected: true }], +}; + +const mockEmptyOrderList: OrderHistoryListView = { + orders: [], + pagination: { totalResults: 0, totalPages: 1 }, +}; + +@Pipe({ + name: 'cxUrl', +}) +class MockUrlPipe implements PipeTransform { + transform() {} +} + +class MockService implements Partial { + getOrderHistoryList(): Observable { + return of(mockOrders); + } + getOrderHistoryListLoaded(): Observable { + return of(true); + } + loadOrderList( + _pageSize: number, + _currentPage?: number, + _sort?: string + ): void {} + clearOrderList() {} +} + +class MockRoutingService { + go() {} +} + +class MockTranslationService { + translate(): Observable { + return EMPTY; + } +} + +describe(' MyAccountV2OrdersComponent', () => { + let component: MyAccountV2OrdersComponent; + let fixture: ComponentFixture; + let service: MyAccountV2OrderHistoryService; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule, I18nTestingModule, FeaturesConfigModule], + declarations: [MyAccountV2OrdersComponent, MockUrlPipe], + providers: [ + { provide: RoutingService, useClass: MockRoutingService }, + { provide: MyAccountV2OrderHistoryService, useClass: MockService }, + { provide: TranslationService, useClass: MockTranslationService }, + ], + }).compileComponents(); + service = TestBed.inject(MyAccountV2OrderHistoryService); + }) + ); + + beforeEach(() => { + fixture = TestBed.createComponent(MyAccountV2OrdersComponent); + component = fixture.componentInstance; + }); + + it('should create the component', () => { + expect(component).toBeTruthy(); + }); + + it('should load header with show more', () => { + component.orders$ = of(mockOrders); + component.isLoaded$.next(true); + fixture.detectChanges(); + const heading = fixture.debugElement.query( + By.css('.cx-my-account-view-heading') + ); + expect(heading).not.toBeNull(); + const link = fixture.debugElement.query(By.css('#show-more-orders')); + expect(link).not.toBeNull(); + }); + + it('should show orders', () => { + component.orders$ = of(mockOrders); + component.isLoaded$.next(true); + fixture.detectChanges(); + const details = fixture.debugElement.query( + By.css('.cx-my-account-view-order') + ); + expect(details).not.toBeNull(); + const noOrder = fixture.debugElement.query( + By.css('.cx-my-account-no-order') + ); + expect(noOrder).toBeNull(); + const spinner = fixture.debugElement.query(By.css('.cx-spinner')); + expect(spinner).toBeNull(); + }); + + it('should show no orders', () => { + component.orders$ = of(mockEmptyOrderList); + component.isLoaded$.next(true); + fixture.detectChanges(); + const details = fixture.debugElement.query( + By.css('.cx-my-account-view-order') + ); + expect(details).toBeNull(); + const noOrder = fixture.debugElement.query( + By.css('.cx-my-account-no-order') + ); + expect(noOrder).not.toBeNull(); + const spinner = fixture.debugElement.query(By.css('.cx-spinner')); + expect(spinner).toBeNull(); + }); + + it('should show spinner', () => { + component.orders$ = of({ pagination: { totalResults: 0 } }); + component.isLoaded$.next(false); + fixture.detectChanges(); + const details = fixture.debugElement.query( + By.css('.cx-my-account-view-order') + ); + expect(details).toBeNull(); + const noOrder = fixture.debugElement.query( + By.css('.cx-my-account-no-order') + ); + expect(noOrder).toBeNull(); + const spinner = fixture.debugElement.query(By.css('.cx-spinner')); + expect(spinner).not.toBeNull(); + }); + + describe('should get a product from an order', () => { + it('should return a product with name and images', () => { + let result = component.getProduct(mockOrders.orders[0]); + expect(result.name).toEqual('b1'); + }); + it('should return the first product as no order has images', () => { + let result = component.getProduct(mockOrders.orders[1]); + expect(result.name).toEqual('a1'); + }); + }); + + it('on destroy', () => { + spyOn(service, 'clearOrderList').and.stub(); + component.ngOnDestroy(); + expect(service.clearOrderList).toHaveBeenCalled(); + }); +}); diff --git a/feature-libs/order/components/my-account-v2/my-account-v2-orders.component.ts b/feature-libs/order/components/my-account-v2/my-account-v2-orders.component.ts new file mode 100644 index 00000000000..d420dcfac2f --- /dev/null +++ b/feature-libs/order/components/my-account-v2/my-account-v2-orders.component.ts @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Component, inject, OnDestroy } from '@angular/core'; +import { Product } from '@spartacus/core'; +import { MyAccountV2OrderHistoryService } from '@spartacus/order/core'; +import { Order, OrderHistoryListView } from '@spartacus/order/root'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +@Component({ + selector: 'cx-my-account-v2-orders', + templateUrl: './my-account-v2-orders.component.html', +}) +export class MyAccountV2OrdersComponent implements OnDestroy { + protected service = inject(MyAccountV2OrderHistoryService); + protected PAGE_SIZE = 3; + orders$: Observable = this.service + .getOrderHistoryList(this.PAGE_SIZE) + .pipe(tap(() => this.isLoaded$.next(true))); + isLoaded$ = new BehaviorSubject(false); + getProduct(order: Order): Product | undefined { + if (order.entries) { + for (const entry of order.entries) { + if (entry.product && entry.product.name && entry.product.images) { + return entry.product; + } + } + return order.entries[0].product; + } + } + ngOnDestroy(): void { + this.isLoaded$.next(false); + this.service.clearOrderList(); + } +} diff --git a/feature-libs/order/components/my-account-v2/my-account-v2-orders.module.ts b/feature-libs/order/components/my-account-v2/my-account-v2-orders.module.ts new file mode 100644 index 00000000000..c09ef4692d9 --- /dev/null +++ b/feature-libs/order/components/my-account-v2/my-account-v2-orders.module.ts @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { + UrlModule, + I18nModule, + provideDefaultConfig, + CmsConfig, + AuthGuard, +} from '@spartacus/core'; +import { MediaModule, SpinnerModule } from '@spartacus/storefront'; +import { MyAccountV2OrdersComponent } from './my-account-v2-orders.component'; + +@NgModule({ + imports: [ + CommonModule, + RouterModule, + FormsModule, + SpinnerModule, + UrlModule, + I18nModule, + MediaModule, + ], + declarations: [MyAccountV2OrdersComponent], + exports: [MyAccountV2OrdersComponent], + providers: [ + provideDefaultConfig({ + cmsComponents: { + MyAccountViewOrderComponent: { + component: MyAccountV2OrdersComponent, + guards: [AuthGuard], + }, + }, + }), + ], +}) +export class MyAccountV2OrdersModule {} diff --git a/feature-libs/order/components/order-components.module.ts b/feature-libs/order/components/order-components.module.ts index 1f7bdd44e0d..0e508ae3db7 100755 --- a/feature-libs/order/components/order-components.module.ts +++ b/feature-libs/order/components/order-components.module.ts @@ -10,6 +10,7 @@ import { OrderCancellationModule, OrderReturnModule, } from './amend-order/index'; +import { MyAccountV2OrdersModule } from './my-account-v2'; import { OrderConfirmationModule } from './order-confirmation/order-confirmation.module'; import { OrderDetailsModule } from './order-details/order-details.module'; import { OrderHistoryModule } from './order-history/order-history.module'; @@ -30,6 +31,7 @@ import { ReturnRequestListModule } from './return-request-list/order-return-requ ReturnRequestListModule, ReturnRequestDetailModule, OrderConfirmationModule, + MyAccountV2OrdersModule, ], providers: [ { diff --git a/feature-libs/order/components/public_api.ts b/feature-libs/order/components/public_api.ts index 9ff9d1744a7..f66ffacee30 100644 --- a/feature-libs/order/components/public_api.ts +++ b/feature-libs/order/components/public_api.ts @@ -17,3 +17,4 @@ export * from './replenishment-order-history/index'; export * from './return-request-detail/index'; export * from './return-request-list/order-return-request-list.component'; export * from './return-request-list/order-return-request-list.module'; +export * from './my-account-v2/index'; diff --git a/feature-libs/order/core/facade/my-account-v2-order-history.service.spec.ts b/feature-libs/order/core/facade/my-account-v2-order-history.service.spec.ts index a219072a2c9..7acd1573431 100644 --- a/feature-libs/order/core/facade/my-account-v2-order-history.service.spec.ts +++ b/feature-libs/order/core/facade/my-account-v2-order-history.service.spec.ts @@ -289,6 +289,8 @@ describe('MyAccountV2OrderHistoryService', () => { returnRequests: [], entries: undefined, unconsignedEntries: undefined, + returnable: undefined, + totalItems: undefined, }, { code: orderCode2, @@ -297,6 +299,8 @@ describe('MyAccountV2OrderHistoryService', () => { returnRequests: [], entries: undefined, unconsignedEntries: undefined, + returnable: undefined, + totalItems: undefined, }, ], pagination: {}, diff --git a/feature-libs/order/core/facade/my-account-v2-order-history.service.ts b/feature-libs/order/core/facade/my-account-v2-order-history.service.ts index 67b9c5d53d1..0e246d26a93 100644 --- a/feature-libs/order/core/facade/my-account-v2-order-history.service.ts +++ b/feature-libs/order/core/facade/my-account-v2-order-history.service.ts @@ -103,6 +103,8 @@ export class MyAccountV2OrderHistoryService { return this.getOrderDetailsWithTracking(order?.code ?? '').pipe( map((orderDetail: OrderView | undefined) => { /** filling extra fields ---> */ + orderView.returnable = orderDetail?.returnable; + orderView.totalItems = orderDetail?.totalItems; orderView.entries = orderDetail?.entries; orderView.consignments = orderDetail?.consignments; orderView.unconsignedEntries = orderDetail?.unconsignedEntries; diff --git a/feature-libs/order/root/model/order-view.model.ts b/feature-libs/order/root/model/order-view.model.ts index 92e510fb3ba..eb4140caea9 100644 --- a/feature-libs/order/root/model/order-view.model.ts +++ b/feature-libs/order/root/model/order-view.model.ts @@ -25,6 +25,8 @@ export interface OrderHistoryView extends OrderHistory { consignments?: ConsignmentView[]; unconsignedEntries?: OrderEntry[]; returnRequests?: ReturnRequest[]; + totalItems?: number; + returnable?: boolean; } export interface OrderHistoryListView extends OrderHistoryList { diff --git a/feature-libs/order/root/order-root.module.ts b/feature-libs/order/root/order-root.module.ts index bae8e0211ec..53557a576e7 100644 --- a/feature-libs/order/root/order-root.module.ts +++ b/feature-libs/order/root/order-root.module.ts @@ -64,6 +64,7 @@ export function defaultOrderComponentsConfig(): CmsConfig { 'ReplenishmentConfirmationOverviewComponent', 'ReplenishmentConfirmationItemsComponent', 'ReplenishmentConfirmationTotalsComponent', + 'MyAccountViewOrderComponent', ], dependencies: [CART_BASE_FEATURE], }, diff --git a/feature-libs/order/styles/components/_index.scss b/feature-libs/order/styles/components/_index.scss index 6aa7c0c6685..dba4ec95642 100644 --- a/feature-libs/order/styles/components/_index.scss +++ b/feature-libs/order/styles/components/_index.scss @@ -2,3 +2,4 @@ @import './order-overview/index'; @import './order-details/index'; @import './replenishment-order/index'; +@import './my-account-v2/index'; diff --git a/feature-libs/order/styles/components/my-account-v2/_index.scss b/feature-libs/order/styles/components/my-account-v2/_index.scss new file mode 100644 index 00000000000..12b4baf71a5 --- /dev/null +++ b/feature-libs/order/styles/components/my-account-v2/_index.scss @@ -0,0 +1,23 @@ +@import './my-account-v2-order'; + +$myaccount-view-order-allowlist: cx-my-account-v2-orders !default; + +$skipComponentStyles: () !default; + +@each $selector in $myaccount-view-order-allowlist { + #{$selector} { + // skip selectors if they're added to the $skipComponentStyles list + @if (index($skipComponentStyles, $selector) == null) { + @extend %#{$selector} !optional; + } + } +} + +// add body specific selectors +body { + @each $selector in $myaccount-view-order-allowlist { + @if (index($skipComponentStyles, $selector) == null) { + @extend %#{$selector}__body !optional; + } + } +} diff --git a/feature-libs/order/styles/components/my-account-v2/_my-account-v2-order.scss b/feature-libs/order/styles/components/my-account-v2/_my-account-v2-order.scss new file mode 100644 index 00000000000..f48ca395b0b --- /dev/null +++ b/feature-libs/order/styles/components/my-account-v2/_my-account-v2-order.scss @@ -0,0 +1,106 @@ +%cx-my-account-v2-orders { + margin: 1rem 0rem; + border: 1px solid var(--cx-color-medium); + .cx-my-account-view-header { + display: flex; + justify-content: space-between; + margin: 1rem; //top-right-bottom-left + .cx-my-account-view-heading { + font-size: 18px; + font-weight: 700; + } + .cx-my-account-view-show-more { + font-size: 16px; + font-weight: 600; + } + } + .cx-my-account-view-body { + .cx-my-account-view-order { + border-collapse: collapse; + margin: 0; + .cx-my-account-view-order-header { + display: flex; + justify-content: space-between; + + margin: 1rem 1rem 0 1rem; //top-right-bottom-left + padding: 15px 30px; //30px 30px; + background-color: var(--cx-color-light); + border: 1px solid var(--cx-color-medium); + border-radius: 0; + .cx-my-account-view-status { + font-size: 18px; + font-weight: 700; + } + .cx-my-account-view-code { + font-size: 14px; + font-weight: 400; + color: var(--cx-color-secondary); + } + } + .cx-my-account-view-order-body { + display: flex; + justify-content: space-between; + + margin: 0 1rem 1.5rem 1rem; //top-right-bottom-left + padding: 15px; + border: 1px solid var(--cx-color-medium); + &:first-child { + border-radius: 0; + } + + .cx-my-account-view-order-column-1 { + width: 70%; + .cx-my-account-view-order-column-1-image { + float: left; + margin: 1 rem; + padding: 15px; + .cx-my-account-view-order-img { + width: 124px; + height: 124px; + display: inline-end; + } + } + .cx-my-account-view-order-column-1-details { + .cx-my-account-view-order-column-1-details-top { + margin: 1 rem; + padding: 15px 15px 15px 20px; + .cx-my-account-view-product-name { + font-size: 16px; + font-weight: 600; + } + .cx-my-account-view-purchased-on { + font-size: 14px; + font-weight: 400; + } + .cx-my-account-view-item-count { + font-size: 14px; + font-weight: 400; + } + } + .cx-my-account-view-order-column-1-details-bottom { + padding: 15px 15px 15px 20px; + .cx-my-account-view-total-price { + font-size: 16px; + font-weight: 600; + } + } + } + } + .cx-my-account-view-order-column-2 { + padding: 15px; + width: 30%; + display: flex; + justify-content: right; + color: var(--cx-color-secondary); + font-size: 16px; + font-weight: 600; + align-items: flex-end; + } + } + } + } + .cx-my-account-no-order { + margin: 1rem; + padding: 15px; + } +} diff --git a/feature-libs/user/account/assets/translations/en/user-account.ts b/feature-libs/user/account/assets/translations/en/user-account.ts index 46e4ee96533..eb8a0874777 100644 --- a/feature-libs/user/account/assets/translations/en/user-account.ts +++ b/feature-libs/user/account/assets/translations/en/user-account.ts @@ -25,4 +25,7 @@ export const userAccount = { userGreeting: 'Hi, {{name}}', signInRegister: 'Sign In / Register', }, + myAccountV2User: { + signOut: 'Sign Out', + }, }; diff --git a/feature-libs/user/account/assets/translations/translations.ts b/feature-libs/user/account/assets/translations/translations.ts index 6247806ae3a..759257fb3e7 100644 --- a/feature-libs/user/account/assets/translations/translations.ts +++ b/feature-libs/user/account/assets/translations/translations.ts @@ -12,5 +12,5 @@ export const userAccountTranslations: TranslationResources = { }; export const userAccountTranslationChunksConfig: TranslationChunksConfig = { - userAccount: ['loginForm', 'miniLogin'], + userAccount: ['loginForm', 'miniLogin', 'myAccountV2User'], }; diff --git a/feature-libs/user/account/components/my-account-v2-user/index.ts b/feature-libs/user/account/components/my-account-v2-user/index.ts new file mode 100644 index 00000000000..1ca699614ae --- /dev/null +++ b/feature-libs/user/account/components/my-account-v2-user/index.ts @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './my-account-v2-user.component'; +export * from './my-account-v2-user.module'; diff --git a/feature-libs/user/account/components/my-account-v2-user/my-account-v2-user.component.html b/feature-libs/user/account/components/my-account-v2-user/my-account-v2-user.component.html new file mode 100644 index 00000000000..6add4810f2e --- /dev/null +++ b/feature-libs/user/account/components/my-account-v2-user/my-account-v2-user.component.html @@ -0,0 +1,14 @@ + diff --git a/feature-libs/user/account/components/my-account-v2-user/my-account-v2-user.component.spec.ts b/feature-libs/user/account/components/my-account-v2-user/my-account-v2-user.component.spec.ts new file mode 100644 index 00000000000..a0953a80608 --- /dev/null +++ b/feature-libs/user/account/components/my-account-v2-user/my-account-v2-user.component.spec.ts @@ -0,0 +1,95 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MyAccountV2UserComponent } from './my-account-v2-user.component'; +import { + AuthService, + I18nTestingModule, + RoutingService, + User, +} from '@spartacus/core'; +import { ActivatedRoute } from '@angular/router'; +import { UserAccountFacade } from '../../root/facade'; +import { Observable, of } from 'rxjs'; +import { RouterTestingModule } from '@angular/router/testing'; +import createSpy = jasmine.createSpy; +import { Pipe, PipeTransform } from '@angular/core'; +import { By } from '@angular/platform-browser'; + +class MockAuthService { + login = createSpy(); + isUserLoggedIn(): Observable { + return of(true); + } +} + +@Pipe({ + name: 'cxUrl', +}) +class MockUrlPipe implements PipeTransform { + transform(): void {} +} + +const mockUserDetails: User = { + displayUid: 'Display Uid', + firstName: 'First', + lastName: 'Last', + name: 'First Last', + uid: 'UID', +}; + +class MockRoutingService { + go = createSpy('go'); +} +class MockUserAccountFacade { + get(): Observable { + return of(mockUserDetails); + } + load(): void {} +} + +describe('MyAccountV2UserComponent', () => { + let component: MyAccountV2UserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [RouterTestingModule, I18nTestingModule], + declarations: [MyAccountV2UserComponent, MockUrlPipe], + providers: [ + { + provide: ActivatedRoute, + useValue: { + snapshot: { + firstChild: { + routeConfig: { + canActivate: [{ GUARD_NAME: 'AuthGuard' }], + }, + }, + }, + }, + }, + { provide: RoutingService, useClass: MockRoutingService }, + { provide: UserAccountFacade, useClass: MockUserAccountFacade }, + { provide: AuthService, useClass: MockAuthService }, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MyAccountV2UserComponent); + component = fixture.componentInstance; + component.ngOnInit(); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should see user name when the user is logged in', () => { + expect(fixture.debugElement.query(By.css('.cx-name'))).not.toBeNull(); + }); + + it('should display signout when the user is logged in', () => { + expect(fixture.debugElement.query(By.css('.cx-sign-out'))).not.toBeNull(); + }); +}); diff --git a/feature-libs/user/account/components/my-account-v2-user/my-account-v2-user.component.ts b/feature-libs/user/account/components/my-account-v2-user/my-account-v2-user.component.ts new file mode 100644 index 00000000000..38e1d9889f5 --- /dev/null +++ b/feature-libs/user/account/components/my-account-v2-user/my-account-v2-user.component.ts @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Component } from '@angular/core'; +import { LoginComponent } from '../login'; + +@Component({ + selector: 'cx-my-account-v2-user', + templateUrl: './my-account-v2-user.component.html', +}) +export class MyAccountV2UserComponent extends LoginComponent {} diff --git a/feature-libs/user/account/components/my-account-v2-user/my-account-v2-user.module.ts b/feature-libs/user/account/components/my-account-v2-user/my-account-v2-user.module.ts new file mode 100644 index 00000000000..993bb8c39eb --- /dev/null +++ b/feature-libs/user/account/components/my-account-v2-user/my-account-v2-user.module.ts @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { + AuthGuard, + CmsConfig, + I18nModule, + UrlModule, + provideDefaultConfig, +} from '@spartacus/core'; +import { RouterModule } from '@angular/router'; +import { MyAccountV2UserComponent } from './my-account-v2-user.component'; + +@NgModule({ + providers: [ + provideDefaultConfig({ + cmsComponents: { + MyAccountViewUserComponent: { + component: MyAccountV2UserComponent, + guards: [AuthGuard], + }, + }, + }), + ], + declarations: [MyAccountV2UserComponent], + exports: [MyAccountV2UserComponent], + imports: [CommonModule, RouterModule, UrlModule, I18nModule], +}) +export class MyAccountV2UserModule {} diff --git a/feature-libs/user/account/components/public_api.ts b/feature-libs/user/account/components/public_api.ts index 6f14db335fb..63245972b1f 100644 --- a/feature-libs/user/account/components/public_api.ts +++ b/feature-libs/user/account/components/public_api.ts @@ -8,3 +8,4 @@ export * from './login-form/index'; export * from './login-register/index'; export * from './login/index'; export * from './user-account-component.module'; +export * from './my-account-v2-user/index'; diff --git a/feature-libs/user/account/components/user-account-component.module.ts b/feature-libs/user/account/components/user-account-component.module.ts index 9ab4d81f062..d3e31133d91 100644 --- a/feature-libs/user/account/components/user-account-component.module.ts +++ b/feature-libs/user/account/components/user-account-component.module.ts @@ -8,8 +8,14 @@ import { NgModule } from '@angular/core'; import { LoginFormModule } from './login-form/login-form.module'; import { LoginRegisterModule } from './login-register/login-register.module'; import { LoginModule } from './login/login.module'; +import { MyAccountV2UserModule } from './my-account-v2-user'; @NgModule({ - imports: [LoginModule, LoginFormModule, LoginRegisterModule], + imports: [ + LoginModule, + LoginFormModule, + LoginRegisterModule, + MyAccountV2UserModule, + ], }) export class UserAccountComponentsModule {} diff --git a/feature-libs/user/account/root/model/user.model.ts b/feature-libs/user/account/root/model/user.model.ts index d950f060e2b..98cf1218ebe 100644 --- a/feature-libs/user/account/root/model/user.model.ts +++ b/feature-libs/user/account/root/model/user.model.ts @@ -10,4 +10,5 @@ export interface User { lastName?: string; name?: string; uid?: string; + title?: string; } diff --git a/feature-libs/user/account/root/user-account-root.module.ts b/feature-libs/user/account/root/user-account-root.module.ts index 1922f92f222..2ca81eeeba3 100644 --- a/feature-libs/user/account/root/user-account-root.module.ts +++ b/feature-libs/user/account/root/user-account-root.module.ts @@ -21,6 +21,7 @@ export function defaultUserAccountComponentsConfig(): CmsConfig { 'LoginComponent', 'ReturningCustomerLoginComponent', 'ReturningCustomerRegisterComponent', + 'MyAccountViewUserComponent', ], }, // by default core is bundled together with components diff --git a/feature-libs/user/account/styles/_index.scss b/feature-libs/user/account/styles/_index.scss index 406cd803cda..0dff952259b 100644 --- a/feature-libs/user/account/styles/_index.scss +++ b/feature-libs/user/account/styles/_index.scss @@ -1,2 +1,3 @@ @import './login'; @import './login-form'; +@import './my-account-v2-user'; diff --git a/feature-libs/user/account/styles/_my-account-v2-user.scss b/feature-libs/user/account/styles/_my-account-v2-user.scss new file mode 100644 index 00000000000..1ceedcd6d79 --- /dev/null +++ b/feature-libs/user/account/styles/_my-account-v2-user.scss @@ -0,0 +1,14 @@ +.cx-my-account-v2-user { + border: 1px solid var(--cx-color-light); + width: 250px; + padding: 20px 5px 5px 25px; + gap: 40px; + height: 120px; + margin: 40px 0px 0px 5px; + + .cx-name { + .cx-sign-out { + text-decoration: underline; + } + } +} diff --git a/projects/schematics/src/add-spartacus/spartacus-features.ts b/projects/schematics/src/add-spartacus/spartacus-features.ts index 140d4df5760..d041f9a9864 100644 --- a/projects/schematics/src/add-spartacus/spartacus-features.ts +++ b/projects/schematics/src/add-spartacus/spartacus-features.ts @@ -130,6 +130,7 @@ function configureSpartacusModules( 'PaymentMethodsModule,', 'NotificationPreferenceModule,', 'MyInterestsModule,', + 'MyAccountV2Module', 'StockNotificationModule,', 'ConsentManagementModule,', 'MyCouponsModule,', @@ -147,6 +148,7 @@ function configureSpartacusModules( 'PaymentMethodsModule', 'NotificationPreferenceModule', 'MyInterestsModule', + 'MyAccountV2Module', 'StockNotificationModule', 'ConsentManagementModule', 'MyCouponsModule', diff --git a/projects/schematics/src/shared/constants.ts b/projects/schematics/src/shared/constants.ts index 955cf8cdb54..ad910296b82 100644 --- a/projects/schematics/src/shared/constants.ts +++ b/projects/schematics/src/shared/constants.ts @@ -866,6 +866,7 @@ export const LOGIN_MODULE = 'LoginModule'; export const LOGIN_FORM_MODULE = 'LoginFormModule'; export const LOGIN_REGISTER_COMPONENT = 'LoginRegisterComponent'; export const LOGIN_REGISTER_MODULE = 'LoginRegisterModule'; + export const UPDATE_EMAIL_MODULE = 'UpdateEmailModule'; export const UPDATE_PASSWORD_MODULE = 'UpdatePasswordModule'; export const UPDATE_PROFILE_MODULE = 'UpdateProfileModule'; diff --git a/projects/schematics/src/wrapper-module/__snapshots__/index_spec.ts.snap b/projects/schematics/src/wrapper-module/__snapshots__/index_spec.ts.snap index 6ad52a46944..199e2feaae2 100644 --- a/projects/schematics/src/wrapper-module/__snapshots__/index_spec.ts.snap +++ b/projects/schematics/src/wrapper-module/__snapshots__/index_spec.ts.snap @@ -69,7 +69,7 @@ export class CheckoutWrapperModule { exports[`Spartacus Wrapper Module Schematics: ng g @spartacus/schematics:wrapper-module Checkout and DP Should order the imports in the wrapper and Spartacus features modules 1`] = ` "import { NgModule } from '@angular/core'; import { AnonymousConsentsModule, AuthModule, CostCenterOccModule, ExternalRoutesModule, ProductModule, ProductOccModule, UserModule, UserOccModule } from "@spartacus/core"; -import { AddressBookModule, AnonymousConsentManagementBannerModule, AnonymousConsentsDialogModule, BannerCarouselModule, BannerModule, BreadcrumbModule, CategoryNavigationModule, CmsParagraphModule, ConsentManagementModule, FooterNavigationModule, HamburgerMenuModule, HomePageEventModule, LinkModule, LoginRouteModule, LogoutModule, MyCouponsModule, MyInterestsModule, NavigationEventModule, NavigationModule, NotificationPreferenceModule, PageTitleModule, PaymentMethodsModule, PDFModule, ProductCarouselModule, ProductDetailsPageModule, ProductFacetNavigationModule, ProductImagesModule, ProductIntroModule, ProductListingPageModule, ProductListModule, ProductPageEventModule, ProductReferencesModule, ProductSummaryModule, ProductTabsModule, ScrollToTopModule, SearchBoxModule, SiteContextSelectorModule, StockNotificationModule, TabParagraphContainerModule, VideoModule } from "@spartacus/storefront"; +import { AddressBookModule, AnonymousConsentManagementBannerModule, AnonymousConsentsDialogModule, BannerCarouselModule, BannerModule, BreadcrumbModule, CategoryNavigationModule, CmsParagraphModule, ConsentManagementModule, FooterNavigationModule, HamburgerMenuModule, HomePageEventModule, LinkModule, LoginRouteModule, LogoutModule, MyAccountV2Module, MyCouponsModule, MyInterestsModule, NavigationEventModule, NavigationModule, NotificationPreferenceModule, PageTitleModule, PaymentMethodsModule, PDFModule, ProductCarouselModule, ProductDetailsPageModule, ProductFacetNavigationModule, ProductImagesModule, ProductIntroModule, ProductListingPageModule, ProductListModule, ProductPageEventModule, ProductReferencesModule, ProductSummaryModule, ProductTabsModule, ScrollToTopModule, SearchBoxModule, SiteContextSelectorModule, StockNotificationModule, TabParagraphContainerModule, VideoModule } from "@spartacus/storefront"; import { UserFeatureModule } from './features/user/user-feature.module'; import { CartBaseFeatureModule } from './features/cart/cart-base-feature.module'; import { OrderFeatureModule } from './features/order/order-feature.module'; @@ -102,6 +102,7 @@ import { DigitalPaymentsFeatureModule } from './features/digital-payments/digita PaymentMethodsModule, NotificationPreferenceModule, MyInterestsModule, + MyAccountV2Module, StockNotificationModule, ConsentManagementModule, MyCouponsModule, @@ -454,7 +455,7 @@ exports[`Spartacus Wrapper Module Schematics: ng g @spartacus/schematics:wrapper import { CheckoutModule } from "@spartacus/checkout/base"; import { AnonymousConsentsModule, AuthModule, CostCenterOccModule, ExternalRoutesModule, ProductModule, ProductOccModule, UserModule, UserOccModule } from "@spartacus/core"; import { DigitalPaymentsModule } from "@spartacus/digital-payments"; -import { AddressBookModule, AnonymousConsentManagementBannerModule, AnonymousConsentsDialogModule, BannerCarouselModule, BannerModule, BreadcrumbModule, CategoryNavigationModule, CmsParagraphModule, ConsentManagementModule, FooterNavigationModule, HamburgerMenuModule, HomePageEventModule, LinkModule, LoginRouteModule, LogoutModule, MyCouponsModule, MyInterestsModule, NavigationEventModule, NavigationModule, NotificationPreferenceModule, PageTitleModule, PaymentMethodsModule, PDFModule, ProductCarouselModule, ProductDetailsPageModule, ProductFacetNavigationModule, ProductImagesModule, ProductIntroModule, ProductListingPageModule, ProductListModule, ProductPageEventModule, ProductReferencesModule, ProductSummaryModule, ProductTabsModule, ScrollToTopModule, SearchBoxModule, SiteContextSelectorModule, StockNotificationModule, TabParagraphContainerModule, VideoModule } from "@spartacus/storefront"; +import { AddressBookModule, AnonymousConsentManagementBannerModule, AnonymousConsentsDialogModule, BannerCarouselModule, BannerModule, BreadcrumbModule, CategoryNavigationModule, CmsParagraphModule, ConsentManagementModule, FooterNavigationModule, HamburgerMenuModule, HomePageEventModule, LinkModule, LoginRouteModule, LogoutModule, MyAccountV2Module, MyCouponsModule, MyInterestsModule, NavigationEventModule, NavigationModule, NotificationPreferenceModule, PageTitleModule, PaymentMethodsModule, PDFModule, ProductCarouselModule, ProductDetailsPageModule, ProductFacetNavigationModule, ProductImagesModule, ProductIntroModule, ProductListingPageModule, ProductListModule, ProductPageEventModule, ProductReferencesModule, ProductSummaryModule, ProductTabsModule, ScrollToTopModule, SearchBoxModule, SiteContextSelectorModule, StockNotificationModule, TabParagraphContainerModule, VideoModule } from "@spartacus/storefront"; import { CartBaseFeatureModule } from './features/cart/cart-base-feature.module'; import { CheckoutFeatureModule } from './features/checkout/checkout-feature.module'; import { DigitalPaymentsFeatureModule } from './features/digital-payments/digital-payments-feature.module'; @@ -487,6 +488,7 @@ import { UserFeatureModule } from './features/user/user-feature.module'; PaymentMethodsModule, NotificationPreferenceModule, MyInterestsModule, + MyAccountV2Module, StockNotificationModule, ConsentManagementModule, MyCouponsModule, diff --git a/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-landing-page.e2e.cy.ts b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-landing-page.e2e.cy.ts new file mode 100644 index 00000000000..24f6b245308 --- /dev/null +++ b/projects/storefrontapp-e2e-cypress/cypress/e2e/regression/my-account/my-account-v2-landing-page.e2e.cy.ts @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { fillLoginForm } from '../../../helpers/auth-forms'; +import { viewportContext } from '../../../helpers/viewport-context'; +import { isolateTests } from '../../../support/utils/test-isolation'; + +describe('My Account Version-2 Landing Page', { testIsolation: false }, () => { + viewportContext(['desktop'], () => { + isolateTests(); + before(() => { + cy.window().then((win) => win.sessionStorage.clear()); + cy.visit('/'); + }); + + beforeEach(() => { + cy.restoreLocalStorage(); + }); + + it('should navigate to login page and SignIn with user details', () => { + cy.findByText(/Sign in \/ Register/i).click(); + fillLoginForm({ username: 'cdp.user@sap.com', password: 'Test@1' }); + }); + + it('should navigate to My Account Landing page', () => { + cy.get('[aria-label="My Account"]').click(); + cy.get('.wrapper').contains('My Account').click(); + cy.get('cx-my-account-v2-navigation').contains('Customer Service'); + cy.get('cx-my-account-v2-navigation').contains('Order Information'); + cy.get('cx-my-account-v2-navigation').contains('Account Information'); + cy.get('cx-my-account-v2-orders').contains('Orders And Returns'); + cy.get('cx-my-account-v2-customer-ticketing').contains( + 'Customer Service' + ); + }); + + it('should navigate to Customer Service Requests', () => { + cy.get('cx-my-account-v2-navigation') + .findByText(/Requests/i) + .click(); + cy.get('cx-breadcrumb').contains('Customer Service'); + cy.go(-1); + }); + + it('should navigate to Personal Details', () => { + cy.get('cx-my-account-v2-navigation') + .findByText(/Personal Details/i) + .click(); + cy.get('cx-breadcrumb').contains('Update Personal Details'); + cy.go(-1); + }); + + it('should navigate to Order History on click of Show More', () => { + cy.get('.cx-my-account-view-show-more').click(); + cy.get('cx-breadcrumb').contains('Order History'); + cy.go(-1); + }); + + it('should navigate to Customer Service on click of Show More', () => { + cy.get('.cx-my-account-customer-ticket-show-more').click(); + cy.get('cx-breadcrumb').contains('Customer Service'); + cy.go(-1); + }); + + afterEach(() => { + cy.saveLocalStorage(); + }); + }); +}); diff --git a/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts b/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts index d910c0d57fb..2defc682a50 100644 --- a/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts +++ b/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts @@ -34,6 +34,7 @@ import { LogoutModule, MyCouponsModule, MyInterestsModule, + MyAccountV2Module, NavigationEventModule, NavigationModule, NotificationPreferenceModule, @@ -167,6 +168,7 @@ if (environment.requestedDeliveryDate) { PaymentMethodsModule, NotificationPreferenceModule, MyInterestsModule, + MyAccountV2Module, StockNotificationModule, ConsentManagementModule, MyCouponsModule, diff --git a/projects/storefrontlib/cms-components/myaccount/index.ts b/projects/storefrontlib/cms-components/myaccount/index.ts index 7a5e9c0c142..24200d0f905 100644 --- a/projects/storefrontlib/cms-components/myaccount/index.ts +++ b/projects/storefrontlib/cms-components/myaccount/index.ts @@ -12,3 +12,4 @@ export * from './my-interests/my-interests.module'; export * from './notification-preference/index'; export * from './payment-methods/payment-methods.component'; export * from './payment-methods/payment-methods.module'; +export * from './my-account-v2/index'; diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/index.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/index.ts new file mode 100644 index 00000000000..c790a439aa0 --- /dev/null +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/index.ts @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './my-account-v2-navigation/index'; +export * from './my-account-v2.module'; diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/index.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/index.ts new file mode 100644 index 00000000000..c4e85610cc0 --- /dev/null +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/index.ts @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './my-account-v2-navigation.component'; +export * from './my-account-v2-navigation.module'; diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/my-account-v2-navigation.component.html b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/my-account-v2-navigation.component.html new file mode 100644 index 00000000000..8dd9bd007a2 --- /dev/null +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/my-account-v2-navigation.component.html @@ -0,0 +1,7 @@ + diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/my-account-v2-navigation.component.spec.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/my-account-v2-navigation.component.spec.ts new file mode 100644 index 00000000000..cbee20ce55c --- /dev/null +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/my-account-v2-navigation.component.spec.ts @@ -0,0 +1,71 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { MyAccountV2NavigationComponent } from './my-account-v2-navigation.component'; +import { + CmsComponentData, + NavigationNode, + NavigationService, +} from '@spartacus/storefront'; +import { CmsNavigationComponent } from '@spartacus/core'; +import { of } from 'rxjs'; +import createSpy = jasmine.createSpy; +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'cx-navigation-ui', + template: '', +}) +class MockNavigationUIComponent { + @Input() + node: NavigationNode; + @Input() + navAriaLabel: string; +} +const mockCmsComponentData = { + styleClass: 'footer-styling', +}; + +const MockCmsNavigationComponent = >{ + data$: of(mockCmsComponentData), +}; + +describe('MyAccountV2NavigationComponent', () => { + let component: MyAccountV2NavigationComponent; + let fixture: ComponentFixture; + + const mockNavigationService = { + createNavigation: createSpy().and.returnValue(of(null)), + }; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + providers: [ + { + provide: NavigationService, + useValue: mockNavigationService, + }, + { + provide: CmsComponentData, + useValue: MockCmsNavigationComponent, + }, + ], + declarations: [ + MyAccountV2NavigationComponent, + MockNavigationUIComponent, + ], + }).compileComponents(); + }) + ); + + beforeEach(() => { + fixture = TestBed.createComponent(MyAccountV2NavigationComponent); + component = fixture.componentInstance; + component.styleClass$ = of(mockCmsComponentData.styleClass); + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/my-account-v2-navigation.component.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/my-account-v2-navigation.component.ts new file mode 100644 index 00000000000..b47d4507d67 --- /dev/null +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/my-account-v2-navigation.component.ts @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Component } from '@angular/core'; +import { NavigationComponent } from '../../../navigation'; + +@Component({ + selector: 'cx-my-account-v2-navigation', + templateUrl: './my-account-v2-navigation.component.html', +}) +export class MyAccountV2NavigationComponent extends NavigationComponent {} diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/my-account-v2-navigation.module.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/my-account-v2-navigation.module.ts new file mode 100644 index 00000000000..34fc5cbd78d --- /dev/null +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2-navigation/my-account-v2-navigation.module.ts @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MyAccountV2NavigationComponent } from './my-account-v2-navigation.component'; +import { + AuthGuard, + CmsConfig, + I18nModule, + provideDefaultConfig, +} from '@spartacus/core'; +import { NavigationModule } from '../../../navigation/navigation/navigation.module'; + +@NgModule({ + providers: [ + provideDefaultConfig({ + cmsComponents: { + MyAccountSideNavigationComponent: { + component: MyAccountV2NavigationComponent, + guards: [AuthGuard], + }, + }, + }), + ], + declarations: [MyAccountV2NavigationComponent], + exports: [MyAccountV2NavigationComponent], + imports: [CommonModule, NavigationModule, I18nModule], +}) +export class MyAccountV2NavigationModule {} diff --git a/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2.module.ts b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2.module.ts new file mode 100644 index 00000000000..6fe8e377e75 --- /dev/null +++ b/projects/storefrontlib/cms-components/myaccount/my-account-v2/my-account-v2.module.ts @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2023 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { NgModule } from '@angular/core'; +import { MyAccountV2NavigationModule } from './my-account-v2-navigation'; + +@NgModule({ + imports: [MyAccountV2NavigationModule], +}) +export class MyAccountV2Module {} diff --git a/projects/storefrontlib/cms-components/navigation/navigation/navigation-ui.component.html b/projects/storefrontlib/cms-components/navigation/navigation/navigation-ui.component.html index 3b7f134ad49..b288de7711f 100644 --- a/projects/storefrontlib/cms-components/navigation/navigation/navigation-ui.component.html +++ b/projects/storefrontlib/cms-components/navigation/navigation/navigation-ui.component.html @@ -61,7 +61,7 @@ - + {{ node.title }} diff --git a/projects/storefrontlib/recipes/config/layout-config.ts b/projects/storefrontlib/recipes/config/layout-config.ts index 4f79502182d..e7925cb089b 100644 --- a/projects/storefrontlib/recipes/config/layout-config.ts +++ b/projects/storefrontlib/recipes/config/layout-config.ts @@ -121,5 +121,8 @@ export const layoutConfig: LayoutConfig = { CheckoutLoginPageTemplate: { slots: ['RightContentSlot'], }, + MyAccountViewPageTemplate: { + slots: ['LeftContentSlot', 'RightContentSlot'], + }, }, }; diff --git a/projects/storefrontstyles/scss/_page-template.scss b/projects/storefrontstyles/scss/_page-template.scss index 6a03903ef07..22afdd20efb 100644 --- a/projects/storefrontstyles/scss/_page-template.scss +++ b/projects/storefrontstyles/scss/_page-template.scss @@ -14,7 +14,8 @@ $page-template-allowlist: LandingPage2Template, ContentPage1Template, CategoryPageTemplate, ProductListPageTemplate, ProductGridPageTemplate, SearchResultsListPageTemplate, ProductDetailsPageTemplate, LoginPageTemplate, MultiStepCheckoutSummaryPageTemplate, ErrorPageTemplate, - CheckoutLoginPageTemplate, AccountPageTemplate, OrderConfirmationPageTemplate !default; + CheckoutLoginPageTemplate, AccountPageTemplate, OrderConfirmationPageTemplate, + MyAccountViewPageTemplate !default; @each $selector in $page-template-allowlist { cx-page-layout.#{$selector} { diff --git a/projects/storefrontstyles/scss/components/content/_index.scss b/projects/storefrontstyles/scss/components/content/_index.scss index 99d56f1a8d6..afd58e4bd7d 100644 --- a/projects/storefrontstyles/scss/components/content/_index.scss +++ b/projects/storefrontstyles/scss/components/content/_index.scss @@ -9,4 +9,4 @@ $content-components-allowlist: cx-banner, cx-link, cx-media, cx-carousel, cx-banner-carousel, cx-global-message, cx-navigation-ui, cx-footer-navigation, cx-category-navigation, cx-breadcrumb, cx-page-header, cx-scroll-to-top, - cx-video, cx-pdf !default; + cx-video, cx-pdf, cx-my-account-v2-navigation !default; diff --git a/projects/storefrontstyles/scss/components/content/navigation/_index.scss b/projects/storefrontstyles/scss/components/content/navigation/_index.scss index ef0154c2dee..0b962ded375 100644 --- a/projects/storefrontstyles/scss/components/content/navigation/_index.scss +++ b/projects/storefrontstyles/scss/components/content/navigation/_index.scss @@ -4,3 +4,4 @@ @import './category-navigation'; @import './footer-navigation'; @import './scroll-to-top'; +@import './my-account-v2-navigation'; diff --git a/projects/storefrontstyles/scss/components/content/navigation/_my-account-v2-navigation.scss b/projects/storefrontstyles/scss/components/content/navigation/_my-account-v2-navigation.scss new file mode 100644 index 00000000000..093a78baff1 --- /dev/null +++ b/projects/storefrontstyles/scss/components/content/navigation/_my-account-v2-navigation.scss @@ -0,0 +1,53 @@ +%cx-my-account-v2-navigation { + background: var(--cx-color-inverse); + font-family: sans-serif; + padding: 5px 5px 5px 5px; + width: 250px; + a { + font-size: var(--cx-font-medium); + font-weight: bold; + width: 250px; + height: 50px; + display: flex; + text-indent: 5px; + padding: 16px !important; + &:hover { + color: var(--cx-color-primary); + text-decoration: underline; + } + } + + cx-navigation-ui { + background: var(--cx-color-inverse); + // Node Heading + span { + text-indent: 5px; + font-weight: bold; + display: block; + padding: 10px 5px 15px 5px; + background-color: var(--cx-color-background); + width: 250px; + height: 40px; + margin-top: 20px; + } + + > nav > ul { + > li { + a { + border: 1px solid var(--cx-color-light); + } + margin-bottom: 20px; + } + } + } + // The links in the my-account-v2-navigation are meant to be decorated with an icon. + // Instead of leveraging Spartacus `` component to display icons, + // we use here a pure CSS classes approach. The classes are defined in CMS + // via the `styleClasses` option. The final HTML looks like in the following example: + // Order Information + .fas { + font-family: sans-serif, 'Font Awesome 5 Free'; + font-size: var(--cx-font-medium); + display: inline-block; + } +} diff --git a/projects/storefrontstyles/scss/layout/_index.scss b/projects/storefrontstyles/scss/layout/_index.scss index 833478bee92..3b17c3f65fe 100644 --- a/projects/storefrontstyles/scss/layout/_index.scss +++ b/projects/storefrontstyles/scss/layout/_index.scss @@ -14,6 +14,7 @@ @import './page-templates/account-page'; @import './page-templates/search-results-grid'; @import './page-templates/order-confirmation-page'; +@import './page-templates/myaccount-v2-page'; %CheckoutLoginPageTemplate { @extend %LoginPageTemplate !optional; diff --git a/projects/storefrontstyles/scss/layout/page-templates/_myaccount-v2-page.scss b/projects/storefrontstyles/scss/layout/page-templates/_myaccount-v2-page.scss new file mode 100644 index 00000000000..f1c8949ee6c --- /dev/null +++ b/projects/storefrontstyles/scss/layout/page-templates/_myaccount-v2-page.scss @@ -0,0 +1,33 @@ +%MyAccountViewPageTemplate { + max-width: var(--cx-page-width-max); + margin: auto; + display: flex; + cx-page-slot { + cx-banner { + height: 120px; + margin-top: 40px; + margin-bottom: -500px; + border: 1px solid var(--cx-color-light); + } + } + + @include media-breakpoint-down(md) { + flex-direction: column; + } + .LeftContentSlot { + max-width: 25%; + @include media-breakpoint-down(md) { + width: 100%; + max-width: none; + } + } + + .RightContentSlot { + max-width: 75%; + + @include media-breakpoint-down(md) { + width: 100%; + max-width: none; + } + } +}