Skip to content

Commit

Permalink
[feature] Add cart icon to Masthead for SAP Commerce integration (#12089
Browse files Browse the repository at this point in the history
)

### Related Ticket(s)

[ADCMS-6463](https://jsw.ibm.com/browse/ADCMS-6463)

### Description

Adds a new `<c4d-masthead-cart>` component to the Masthead. Needs to be opted in using `has-cart` attribute on the `<c4d-masthead-container>` component. When opted in, there is internal logic to read a cookie that will determine if the cart should be shown.

### Testing instructions

The cart icon functionality is controlled by two new knobs on the Masthead > Default story. The "show the cart functionality (has-cart)" checkbox toggles whether or not the cart renders at all. The "mock active cart id" will affect the cookie that the cart is looking for to show / hide. Anytime you change the "mock active cart id", you need to toggle the "show the cart functionality (has-cart)" so that the `<c4d-masthead-card>` component will re-render and check the cookie value. The cart icon should only appear when "show the cart functionality (has-cart)" is set **and** there is a non-empty value for "mock active cart id".

Check visuals against [Figma specs](https://www.figma.com/design/aQ0mMVEnxwDDoupkKeaNHc/Ecommerce-Mid-Fidelity-(delivery-file)?node-id=15928-64507&node-type=instance&t=bHBr0X1ykXryXSFP-0).

### Changelog

**New**

- `<c4d-masthead-cart>` component, automaticallly placed in `<c4d-masthead-global-bar>` when opted into via `has-cart` attribute on `<c4d-masthead-container>`

<!-- React and Web Component deploy previews are enabled by default. -->
<!-- To enable additional available deploy previews, apply the following -->
<!-- labels for the corresponding package: -->
<!-- *** "test: e2e": Codesandbox examples and e2e integration tests -->
<!-- *** "package: services": Services -->
<!-- *** "package: utilities": Utilities -->
<!-- *** "RTL": React / Web Components (RTL) -->
<!-- *** "feature flag": React / Web Components (experimental) -->
  • Loading branch information
m4olivei authored Nov 5, 2024
1 parent 9964cf1 commit 90c3d9a
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 5 deletions.
57 changes: 57 additions & 0 deletions packages/services/src/services/SAPCommerce/SAPCommerce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* @license
*
* Copyright IBM Corp. 2024
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import Cookies from 'js-cookie';

const _cookieName = 'activeCartId';

class SAPCommerceAPI {
/**
* Check if the user has an active cart by looking for the non-empty cookie.
*
* @returns {boolean}
*/
static hasActiveCart() {
const activeCartId = SAPCommerceAPI.getActiveCartId();
// Return true if the activeCartId cookie is non-empty.
return activeCartId !== '';
}

/**
* Returns the active cart id.
*
* @returns {string}
* The active cart id if there is one, otherwise empty string.
*/
static getActiveCartId() {
const activeCartId = Cookies.get(_cookieName);
return activeCartId && typeof activeCartId === 'string'
? activeCartId.trim()
: '';
}

/**
* Set the active cart id.
*
* @param {string} activeCartId
* The active cart id.
*/
static setActiveCartId(activeCartId) {
Cookies.set(_cookieName, activeCartId.trim());
}

/**
* Remove the active cart id.
*/
static removeActiveCartId() {
Cookies.remove(_cookieName);
}
}

export default SAPCommerceAPI;
9 changes: 9 additions & 0 deletions packages/services/src/services/SAPCommerce/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @license
*
* Copyright IBM Corp. 2024
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
export { default as SAPCommerceAPI } from './SAPCommerce.js';
3 changes: 2 additions & 1 deletion packages/services/src/services/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright IBM Corp. 2016, 2021
* Copyright IBM Corp. 2016, 2024
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
Expand All @@ -13,6 +13,7 @@ export * from './Locale';
export * from './MarketingSearch';
export * from './MastheadLogo';
export * from './Profile';
export * from './SAPCommerce';
export * from './SearchTypeahead';
export * from './Translation';
export * from './KalturaPlayer';
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
} from './profile-items';
import { C4D_CUSTOM_PROFILE_LOGIN } from '../../../globals/internal/feature-flags';
import readme from './README.stories.mdx';
import SAPCommerceAPI from '@carbon/ibmdotcom-services/es/services/SAPCommerce/SAPCommerce.js';

const userStatuses = {
authenticated: '[email protected]',
Expand Down Expand Up @@ -107,10 +108,21 @@ const enumToArray = (en) =>
.filter((value) => typeof value === 'string')
.map((key) => en[key]);

const setActiveCartId = (activeCartId?: string) => {
if (typeof activeCartId === 'string') {
SAPCommerceAPI.setActiveCartId(activeCartId);
} else {
SAPCommerceAPI.removeActiveCartId();
}
};

export const Default = (args) => {
const {
customProfileLogin,
hasProfile,
hasCart,
mockActiveCartId,
cartLabel,
hasSearch,
hasContact,
initialSearchTerm,
Expand All @@ -120,6 +132,7 @@ export const Default = (args) => {
authMethod,
useMock,
} = args?.MastheadComposite ?? {};
setActiveCartId(mockActiveCartId);
return html`
<style>
${styles}
Expand All @@ -132,6 +145,7 @@ export const Default = (args) => {
initial-search-term="${ifDefined(initialSearchTerm)}"
searchPlaceholder="${ifDefined(searchPlaceholder)}"
has-profile="${hasProfile}"
?has-cart="${hasCart}"
has-search="${hasSearch}"
has-contact="${hasContact}"
.l0Data="${mastheadL0Data}"
Expand All @@ -140,7 +154,8 @@ export const Default = (args) => {
unauthenticatedProfileItems
)}"
custom-profile-login="${customProfileLogin}"
auth-method="${MASTHEAD_AUTH_METHOD.DEFAULT}"></c4d-masthead-container>
auth-method="${MASTHEAD_AUTH_METHOD.DEFAULT}"
cart-label="${ifNonEmpty(cartLabel)}"></c4d-masthead-container>
`
: html`
<c4d-masthead-container
Expand All @@ -150,10 +165,12 @@ export const Default = (args) => {
initial-search-term="${ifDefined(initialSearchTerm)}"
searchPlaceholder="${ifNonEmpty(searchPlaceholder)}"
has-profile="${hasProfile}"
?has-cart="${hasCart}"
has-search="${hasSearch}"
has-contact="${hasContact}"
custom-profile-login="${customProfileLogin}"
auth-method="${authMethod}"></c4d-masthead-container>
auth-method="${authMethod}"
cart-label="${ifNonEmpty(cartLabel)}"></c4d-masthead-container>
`}
`;
};
Expand Down Expand Up @@ -211,6 +228,9 @@ WithCustomTypeahead.story = {
MastheadComposite: {
grouped: 'false',
hasProfile: 'true',
hasCart: false,
mockActiveCartId: '',
cartLabel: '',
hasSearch: 'true',
searchPlaceHolder: 'Search all of IBM',
selectedMenuItem: 'Services & Consulting',
Expand Down Expand Up @@ -317,6 +337,9 @@ withPlatform.story = {
MastheadComposite: {
platform: 'Platform',
hasProfile: 'true',
hasCart: false,
mockActiveCartId: '',
cartLabel: '',
hasSearch: 'true',
searchPlaceHolder: 'Search all of IBM',
selectedMenuItem: 'Services & Consulting',
Expand Down Expand Up @@ -460,6 +483,9 @@ withAlternateLogoAndTooltip.story = {
MastheadComposite: {
platform: null,
hasProfile: 'true',
hasCart: false,
mockActiveCartId: '',
cartLabel: '',
hasSearch: 'true',
searchPlaceholder: 'Search all of IBM',
selectedMenuItem: 'Services & Consulting',
Expand Down Expand Up @@ -506,6 +532,9 @@ WithScopedSearch.story = {
default: {
MastheadComposite: {
hasProfile: 'true',
hasCart: false,
mockActiveCartId: '',
cartLabel: '',
hasSearch: 'true',
searchPlaceHolder: 'Search all of IBM',
selectedMenuItem: 'Services & Consulting',
Expand Down Expand Up @@ -560,6 +589,9 @@ export default {
['true', 'false'],
'true'
),
hasCart: boolean('show the cart functionality (has-cart)', false),
mockActiveCartId: textNullable('mock active cart id', ''),
cartLabel: textNullable('cart label (cart-label)', ''),
hasSearch: select(
'show the search functionality (has-search)',
['true', 'false'],
Expand Down Expand Up @@ -601,6 +633,9 @@ export default {
MastheadComposite: {
platform: null,
hasProfile: 'true',
hasCart: false,
mockActiveCartId: '',
cartLabel: '',
hasSearch: 'true',
initialSearchTerm: '',
searchPlaceholder: 'Search all of IBM',
Expand Down
90 changes: 90 additions & 0 deletions packages/web-components/src/components/masthead/masthead-cart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* @license
*
* Copyright IBM Corp. 2024
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import StableSelectorMixin from '../../globals/mixins/stable-selector';
import { html, LitElement } from 'lit';
import { property, state } from 'lit/decorators.js';
import settings from '@carbon/ibmdotcom-utilities/es/utilities/settings/settings.js';
import { carbonElement as customElement } from '@carbon/web-components/es/globals/decorators/carbon-element';
import ShoppingCart20 from '@carbon/web-components/es/icons/shopping--cart/20.js';
import styles from './masthead.scss';
import LocaleAPI from '@carbon/ibmdotcom-services/es/services/Locale/Locale.js';
import SAPCommerceAPI from '@carbon/ibmdotcom-services/es/services/SAPCommerce/SAPCommerce.js';

const { prefix, stablePrefix: c4dPrefix } = settings;

/**
* The Cart icon in the masthead.
*
* @element c4d-masthead-cart
* @csspart cart-link - The masthead cart link. Usage: `c4d-masthead-cart::part(cart-link)`
*/
@customElement(`${c4dPrefix}-masthead-cart`)
class C4DMastheadCart extends StableSelectorMixin(LitElement) {
/**
* The `aria-label` attribute for the link.
*/
@property({ attribute: 'link-label' })
linkLabel = 'Cart';

/**
* Tracks whether the user has an active cart to control the display.
*/
@state()
hasActiveCart = false;

/**
* Store the locale. Defaults to en-us.
*/
@state()
locale = { lc: 'en', cc: 'us' };

connectedCallback() {
super.connectedCallback();
// Check the relevant cookie for whether the user has an active cart.
this.hasActiveCart = SAPCommerceAPI.hasActiveCart();
// Fetch the locale for the page.
LocaleAPI.getLocale().then((locale) => {
this.locale = locale;
});
}

updated(changedProperties) {
super.updated(changedProperties);
const { hasActiveCart } = this;
if (changedProperties.has('hasActiveCart')) {
this.hidden = !hasActiveCart;
}
}

render() {
const {
linkLabel,
locale: { cc, lc },
} = this;

return html`
<a
part="cart-link"
href="/store/${lc}/${cc}/checkout"
class="${prefix}--header__menu-item ${prefix}--header__menu-title"
aria-label="${linkLabel}"
>${ShoppingCart20()}</a
>
`;
}

static get stableSelector() {
return `${c4dPrefix}--masthead-cart`;
}

static styles = styles;
}

export default C4DMastheadCart;
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import './masthead-contact';
import './masthead-global-bar';
import './masthead-profile';
import './masthead-profile-item';
import './masthead-cart';
import './megamenu';
import './megamenu-heading';
import './megamenu-top-nav-menu';
Expand Down Expand Up @@ -1179,6 +1180,13 @@ class C4DMastheadComposite extends HostListenerMixin(LitElement) {
`;
}

protected _renderCart() {
const { hasCart, cartLabel } = this;
return hasCart
? html`<c4d-masthead-cart link-label="${cartLabel}"></c4d-masthead-cart>`
: undefined;
}

/**
* Gets the appropriate profile items for the current masthead state.
*
Expand Down Expand Up @@ -1503,6 +1511,18 @@ class C4DMastheadComposite extends HostListenerMixin(LitElement) {
@property({ type: String, reflect: true, attribute: 'has-contact' })
hasContact = 'true';

/**
* `true` if Cart should be shown.
*/
@property({ type: Boolean, reflect: true, attribute: 'has-cart' })
hasCart = false;

/**
* Label for the cart icon.
*/
@property({ type: String, reflect: true, attribute: 'cart-label' })
cartLabel = 'Cart';

/**
* The selected authentication method, either `profile-api` (default), `cookie`, or `docs-api`.
*/
Expand Down Expand Up @@ -1632,7 +1652,8 @@ class C4DMastheadComposite extends HostListenerMixin(LitElement) {
${this._renderPlatformTitle()}
${!isMobileVersion ? this._renderTopNav() : ''} ${this._renderSearch()}
<c4d-masthead-global-bar ?has-search-active=${activateSearch}>
${this._renderContact()} ${this._renderProfileMenu()}
${this._renderContact()} ${this._renderCart()}
${this._renderProfileMenu()}
</c4d-masthead-global-bar>
${this._renderL1()}
<c4d-megamenu-overlay></c4d-megamenu-overlay>
Expand Down
19 changes: 18 additions & 1 deletion packages/web-components/src/components/masthead/masthead.scss
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ $pos-btn-bottom: calc(
}

:host(#{$c4d-prefix}-masthead-profile),
:host(#{$c4d-prefix}-masthead-contact) {
:host(#{$c4d-prefix}-masthead-contact),
:host(#{$c4d-prefix}-masthead-cart) {
background-color: $background;
inline-size: $spacing-09;

Expand Down Expand Up @@ -304,6 +305,22 @@ $pos-btn-bottom: calc(
}
}

:host(#{$c4d-prefix}-masthead-cart) {
position: relative;

&::after {
position: absolute;
display: block;
border-radius: 50%;
background-color: $button-primary-hover;
block-size: 10px;
content: '';
inline-size: 10px;
inset-block-start: 12px;
inset-inline-end: 10px;
}
}

:host(#{$c4d-prefix}-top-nav-item),
:host(#{$c4d-prefix}-top-nav-menu),
:host(#{$c4d-prefix}-megamenu-top-nav-menu),
Expand Down

0 comments on commit 90c3d9a

Please sign in to comment.