@salla.sa/twilight-components
Version:
Salla Web Component
394 lines (393 loc) • 17.3 kB
JavaScript
/*!
* Crafted with ❤ by Salla
*/
import { Host, h } from "@stencil/core";
// Icons
import ArrowDown from "../../assets/svg/keyboard_arrow_down.svg";
import BellRing from "../../assets/svg/bell-ring.svg";
import OrderIcon from "../../assets/svg/box-bankers.svg";
import PendingOrdersIcon from "../../assets/svg/cart.svg";
import WishListIcon from "../../assets/svg/star.svg";
import ProfileIcon from "../../assets/svg/user-circle.svg";
import LogoutIcon from "../../assets/svg/send-out.svg";
import Cancel from "../../assets/svg/cancel.svg";
import Rate from "../../assets/svg/star2.svg";
import UserCircle from "../../assets/svg/user-circle.svg";
import WalletIcon from "../../assets/svg/wallet.svg";
import loyaltyProgramIcon from "../../assets/svg/gift.svg";
import SettingstIcon from "../../assets/svg/settings.svg";
/**
* @slot trigger - Replaces trigger widget, has replaceable props `{avatar}`, `{hello}`, `{first_name}`, `{last_name}`, `{icon}`.
* @slot login-btn - Replaces the login button, it must be used with `salla.event.dispatch('login::open')` to open the login modal.
*/
export class SallaUserMenu {
constructor() {
this.accountLoading = false;
this.opened = false;
this.notifications = salla.lang.get('common.titles.notifications');
this.orders = salla.lang.get('common.titles.orders');
this.pending_orders = salla.lang.get('common.titles.pending_orders');
this.wishlist = salla.lang.get('common.titles.wishlist');
this.profile = salla.lang.get('common.titles.profile');
this.rating = salla.lang.get('common.titles.rating');
this.wallet = salla.lang.get('common.titles.wallet');
this.settings = salla.lang.get('common.titles.settings');
this.loyalty_program = salla.lang.get('pages.loyalty_program.loyalty_points');
this.logout = salla.lang.get('blocks.header.logout');
this.hello = salla.lang.get('pages.checkout.hello');
this.first_name = salla.storage.get('user.first_name') || '';
this.last_name = salla.storage.get('user.last_name') || '';
this.avatar = salla.storage.get('user.avatar') || salla.url.cdn('images/avatar.png', 40, 40);
this.badges = {
notifications: salla.helpers.number(salla.storage.get('user.notifications') || 0),
pending_orders: salla.helpers.number(salla.storage.get('user.pending_orders') || 0),
};
this.sallaAccountEnabled = false;
this.hasBadges = Number(salla.storage.get('user.pending_orders')) > 0 ||
Number(salla.storage.get('user.notifications')) > 0;
/**
* To display only the list without the dropdown functionality
*/
this.inline = false;
/**
* To display the trigger as an avatar only
*/
this.avatarOnly = false;
/**
* To display the dropdown header in mobile sheet
*/
this.showHeader = false;
/**
* To Make the dropdown menu relative to parent element or not
*/
this.relativeDropdown = false;
/**
* To show the trigger button or not
*/
this.showTrigger = false;
this.onClickOutside = () => {
this.opened = false;
};
this.OrderUpdate = 0;
this.items = {
notifications: BellRing,
orders: OrderIcon,
pending_orders: PendingOrdersIcon,
wishlist: WishListIcon,
wallet: WalletIcon,
loyalty_program: loyaltyProgramIcon,
profile: ProfileIcon
};
}
async componentWillLoad() {
await salla.onReady();
await salla.lang.onLoaded();
this.loadTranslations();
this.initiate();
Salla.event.on('api::token.injected', (token) => this.profileUrl = this.buildProfileUrl(token));
this.sallaAccountEnabled = Salla.config.get('store.features')?.includes('salla-account');
this.items = this.sallaAccountEnabled ? { ...this.items, settings: SettingstIcon } : this.items;
//we need it only in theme-y
if (this.host.hasAttribute('with-rating')) {
this.items.rating = Rate;
}
//we need it to be the last item
this.items.logout = LogoutIcon;
let token = Salla.storage.get('token');
if (!token) {
token = '';
}
this.profileUrl = this.buildProfileUrl(token);
const trigger = this.host.querySelector('[slot="trigger"]');
this.triggerSlot =
'<div class="s-user-menu-trigger"><div class="s-user-menu-avatar-wrap"><img class="s-user-menu-trigger-avatar" src="{avatar}" alt="{first_name}{last_name}" /></div><div class="s-user-menu-trigger-content"><span class="s-user-menu-trigger-hello">{hello}</span><p class="s-user-menu-trigger-name">{first_name} {last_name}</p></div> <i class="s-user-menu-trigger-icon">{icon}</i></div>';
if (!trigger) {
return;
}
this.triggerSlot = trigger.innerHTML;
trigger.innerHTML = this.replaceParams(trigger.innerHTML);
}
loadTranslations() {
this.notifications = Salla.lang.get('common.titles.notifications');
this.orders = Salla.lang.get('common.titles.orders');
this.pending_orders = Salla.lang.get('common.titles.pending_orders');
this.wishlist = Salla.lang.get('common.titles.wishlist');
this.profile = Salla.lang.get('common.titles.profile');
this.hello = Salla.lang.get('pages.checkout.hello');
this.rating = Salla.lang.get('common.titles.rating');
this.wallet = Salla.lang.get('common.titles.wallet');
this.settings = Salla.lang.get('common.titles.settings');
this.loyalty_program = Salla.lang.get('pages.loyalty_program.loyalty_points');
this.logout = Salla.lang.get('blocks.header.logout');
}
initiate() {
if (Salla.config.isGuest()) {
return this.autoMountLoginModal();
}
this.is_loggedIn = true;
/**
* Get Fresh Notifications In These Cases:
* - is notification page, if user already changed the status of his orders (to reset notification badge)
* - is pending orders page, if user already changed the status of his orders (to reset orders badge)
* - is profile page, in case user changed his name or avatar, we need to update it
* - half hour is passed from the last user data fetched
*
* //todo:: update the data in the storage in customer pages
* //todo:: cover two requests in customer pages
* //todo:: make sure to run this only after token is set
*/
const shouldFetchProfile = !this.inline &&
(salla.url.is_page('customer.notifications') ||
salla.url.is_page('customer.orders.index.pending') ||
salla.url.is_page('customer.profile') ||
(Date.now() - (salla.storage.get('user.fetched_at') || 0)) / 1000 / 60 > 30);
if (shouldFetchProfile) {
return this.fetchFreshProfile();
}
salla.event.on('profile::info.fetched', res => {
this.updateProfileState(res);
});
}
autoMountLoginModal() {
if (!this.showTrigger) {
return;
}
if (document.querySelector('salla-login-modal')) {
return;
}
Salla.hooks.mount('body:end', document.createElement('salla-login-modal'));
}
fetchFreshProfile() {
//don't request fetchFreshProfile unless token is injected into the api
if (!salla.api.token) {
salla.log('trying to fetchFreshProfile before injected the token!!');
return;
}
salla.profile.api.info().then(res => {
this.updateProfileState(res);
});
}
updateProfileState(res) {
this.badges = {
notifications: salla.helpers.number(res.data.notifications || 0),
pending_orders: salla.helpers.number(res.data.pending_orders || 0),
};
this.hasBadges = Number(res.data.pending_orders) > 0 || Number(res.data.notifications) > 0;
this.first_name = res.data.first_name;
this.last_name = res.data.last_name;
this.avatar = res.data.avatar || salla.url.cdn('images/avatar.png', 40, 40);
}
async open(e) {
this.opened = !this.opened;
e.stopPropagation();
if (this.opened) {
window.addEventListener('click', this.onClickOutside);
}
}
menuItemClicked(event, itemKey) {
if (itemKey !== 'logout') {
return;
}
event.preventDefault();
salla.auth.logout('sall-user-menu');
}
replaceParams(body) {
return body
.replace(/\{hello\}/g, this.hello)
.replace(/\{first_name\}/g, this.first_name)
.replace(/\{last_name\}/g, this.last_name)
.replace(/\{avatar\}/g, this.avatar)
.replace(/\{icon\}/g, ArrowDown);
}
getTheHeader() {
return (h("div", { class: {
's-user-menu-trigger-slot': true,
's-user-menu-red-dot': this.hasBadges,
's-user-menu-trigger-avatar-only': this.avatarOnly,
}, id: "trigger-slot", onClick: e => this.open(e), onKeyUp: e => this.open(e), innerHTML: this.replaceParams(this.triggerSlot) }));
}
getItemAnchorLinkAttrs(itemKey) {
if (itemKey === 'profile' && this.profileUrl && this.sallaAccountEnabled) {
return { href: this.profileUrl, target: '_blank', rel: 'noopener noreferrer' };
}
// Fix loyalty program URL mapping from master
const urlKey = itemKey === 'loyalty_program' ? 'loyalty' : itemKey;
return { href: Salla.url.get(urlKey) };
}
getMenuItem([itemKey, itemValue], i) {
//todo:: enhancement support slot here
if (itemKey === 'wallet' && !window.can_access_wallet)
return;
if (itemKey === 'loyalty_program' && !Salla.config.get('store.features').includes('loyalty-system'))
return;
return (h("li", { class: {
's-user-menu-dropdown-item': true,
's-user-menu-dropdown-item-logout': i + 1 === Object.entries(this.items).length,
} }, h("a", { ...this.getItemAnchorLinkAttrs(itemKey), class: "s-user-menu-dropdown-item-link", onClick: event => this.menuItemClicked(event, itemKey) }, h("i", { class: "s-user-menu-dropdown-item-prefix", innerHTML: itemValue }), h("span", { class: "s-user-menu-dropdown-item-title" }, this[itemKey]), !['٠', '0', undefined].includes(this.badges[itemKey]) ? (h("span", { class: "s-user-menu-dropdown-item-badge" }, this.badges[itemKey])) : (''))));
}
componentShouldUpdate() {
if (!this.opened) {
window.removeEventListener('click', this.onClickOutside);
}
}
render() {
if (!this.is_loggedIn) {
return (h(Host, null, h("slot", { name: "login-btn" }, h("button", { type: "button", class: "s-user-menu-login-btn", onClick: () => salla.event.dispatch('login::open'), innerHTML: UserCircle }))));
}
if (this.inline) {
return (h(Host, null, h("ul", { class: "s-user-menu-inline" }, Object.entries(this.items).map((item, i) => this.getMenuItem(item, i)))));
}
return (h(Host, null, h("div", { class: {
's-user-menu-wrapper': true,
's-user-menu-relative-dropdown': this.relativeDropdown,
} }, this.getTheHeader(), h("div", { class: { 's-user-menu-toggler': true, opened: this.opened } }, h("div", { class: "s-user-menu-dropdown", onClick: e => e.stopPropagation(), onKeyUp: e => e.stopPropagation() }, this.showHeader ? (h("div", { class: "s-user-menu-dropdown-header" }, h("img", { src: this.avatar, alt: `${this.first_name} ${this.last_name}` }), h("div", { class: "s-user-menu-dropdown-header-content" }, h("span", null, this.hello), h("p", null, this.first_name, " ", this.last_name)), h("button", { type: 'button', class: "s-user-menu-dropdown-header-close", innerHTML: Cancel, onClick: () => { this.opened = false; } }))) : (''), h("ul", { class: "s-user-menu-dropdown-list" }, Object.entries(this.items).map((item, i) => this.getMenuItem(item, i))))))));
}
componentDidLoad() {
document.lazyLoadInstance?.update(this.host.querySelectorAll('.lazy'));
}
buildProfileUrl(token) {
return `${Salla.config.get('account.url', 'https://accounts.salla.com')}/${salla.config.get('user.language_code')}/user?store=${Salla.config.get('store.id')}&info=${encodeURIComponent(token)}`;
}
static get is() { return "salla-user-menu"; }
static get originalStyleUrls() {
return {
"$": ["salla-user-menu.scss"]
};
}
static get styleUrls() {
return {
"$": ["salla-user-menu.css"]
};
}
static get properties() {
return {
"inline": {
"type": "boolean",
"attribute": "inline",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "To display only the list without the dropdown functionality"
},
"getter": false,
"setter": false,
"reflect": true,
"defaultValue": "false"
},
"avatarOnly": {
"type": "boolean",
"attribute": "avatar-only",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "To display the trigger as an avatar only"
},
"getter": false,
"setter": false,
"reflect": true,
"defaultValue": "false"
},
"showHeader": {
"type": "boolean",
"attribute": "show-header",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "To display the dropdown header in mobile sheet"
},
"getter": false,
"setter": false,
"reflect": true,
"defaultValue": "false"
},
"relativeDropdown": {
"type": "boolean",
"attribute": "relative-dropdown",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "To Make the dropdown menu relative to parent element or not"
},
"getter": false,
"setter": false,
"reflect": true,
"defaultValue": "false"
},
"showTrigger": {
"type": "boolean",
"attribute": "show-trigger",
"mutable": true,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "To show the trigger button or not"
},
"getter": false,
"setter": false,
"reflect": true,
"defaultValue": "false"
}
};
}
static get states() {
return {
"accountLoading": {},
"opened": {},
"notifications": {},
"orders": {},
"pending_orders": {},
"wishlist": {},
"profile": {},
"rating": {},
"wallet": {},
"settings": {},
"loyalty_program": {},
"logout": {},
"hello": {},
"first_name": {},
"last_name": {},
"avatar": {},
"is_loggedIn": {},
"badges": {},
"sallaAccountEnabled": {},
"hasBadges": {},
"profileUrl": {},
"OrderUpdate": {}
};
}
static get elementRef() { return "host"; }
}