@retalia/sidebar-navigation 21.1.13 → 21.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +42 -42
  2. package/karma.conf.js +44 -0
  3. package/ng-package.json +8 -0
  4. package/package.json +14 -26
  5. package/{lib → src}/assets/fonts/Effra_Std_It.svg +5123 -5123
  6. package/{lib → src}/assets/fonts/Effra_Std_Md.svg +4128 -4128
  7. package/{lib → src}/assets/fonts/Effra_Std_Rg.svg +4690 -4690
  8. package/{lib → src}/assets/icons/sidebar-burger.svg +3 -3
  9. package/{lib → src}/assets/icons/sidebar-cross.svg +3 -3
  10. package/{lib → src}/assets/icons/subItem-arrow.svg +3 -3
  11. package/{lib → src}/assets/icons/topbar-info.svg +3 -3
  12. package/{lib → src}/assets/icons/topbar-logout.svg +4 -4
  13. package/{lib → src}/assets/icons/topbar-menu-burger.svg +5 -5
  14. package/{lib → src}/assets/icons/topbar-menu-close.svg +3 -3
  15. package/{lib → src}/assets/icons/topbar-notification.svg +3 -3
  16. package/{lib → src}/assets/icons/topbar-profile.svg +4 -4
  17. package/{lib → src}/assets/icons/topbar-settings.svg +3 -3
  18. package/{lib → src}/assets/icons/topmenu-arrow.svg +3 -3
  19. package/{lib → src}/assets/logo/_K3_imagine_White.svg +14 -14
  20. package/src/lib/data/support-routes.ts +56 -0
  21. package/src/lib/data/test-data.ts +628 -0
  22. package/src/lib/data/test-data2.ts +1742 -0
  23. package/src/lib/models/cookie-names.ts +12 -0
  24. package/src/lib/models/environment.ts +4 -0
  25. package/src/lib/models/http-response.ts +3 -0
  26. package/src/lib/models/module.ts +39 -0
  27. package/src/lib/navigation-lib.component.html +20 -0
  28. package/src/lib/navigation-lib.component.scss +30 -0
  29. package/src/lib/navigation-lib.component.ts +26 -0
  30. package/src/lib/navigation-lib.module.ts +26 -0
  31. package/src/lib/navigation-lib.service.ts +9 -0
  32. package/src/lib/services/auth.service.ts +39 -0
  33. package/src/lib/services/cookie.service.ts +33 -0
  34. package/src/lib/services/http.service.ts +26 -0
  35. package/src/lib/services/module.service.ts +153 -0
  36. package/src/lib/services/sub-item.service.ts +56 -0
  37. package/src/lib/sidebar/shop-modal/shop-modal.component.html +17 -0
  38. package/src/lib/sidebar/shop-modal/shop-modal.component.scss +73 -0
  39. package/src/lib/sidebar/shop-modal/shop-modal.component.ts +36 -0
  40. package/src/lib/sidebar/sidebar.component.html +86 -0
  41. package/src/lib/sidebar/sidebar.component.scss +260 -0
  42. package/src/lib/sidebar/sidebar.component.ts +267 -0
  43. package/src/lib/sidebar/sub-item/sub-item.component.html +18 -0
  44. package/src/lib/sidebar/sub-item/sub-item.component.scss +154 -0
  45. package/src/lib/sidebar/sub-item/sub-item.component.ts +61 -0
  46. package/src/lib/topbar/topbar.component.html +29 -0
  47. package/src/lib/topbar/topbar.component.scss +176 -0
  48. package/src/lib/topbar/topbar.component.ts +76 -0
  49. package/src/public-api.ts +14 -0
  50. package/src/styles/app.scss +53 -0
  51. package/src/test.ts +15 -0
  52. package/tsconfig.lib.json +21 -0
  53. package/tsconfig.lib.prod.json +11 -0
  54. package/tsconfig.spec.json +17 -0
  55. package/fesm2022/retalia-sidebar-navigation.mjs +0 -1388
  56. package/fesm2022/retalia-sidebar-navigation.mjs.map +0 -1
  57. package/production-0.0.2.tgz +0 -0
  58. package/types/retalia-sidebar-navigation.d.ts +0 -232
  59. /package/{lib → src}/assets/fonts/Effra_Std_It.eot +0 -0
  60. /package/{lib → src}/assets/fonts/Effra_Std_It.ttf +0 -0
  61. /package/{lib → src}/assets/fonts/Effra_Std_It.woff +0 -0
  62. /package/{lib → src}/assets/fonts/Effra_Std_It.woff2 +0 -0
  63. /package/{lib → src}/assets/fonts/Effra_Std_Md.eot +0 -0
  64. /package/{lib → src}/assets/fonts/Effra_Std_Md.ttf +0 -0
  65. /package/{lib → src}/assets/fonts/Effra_Std_Md.woff +0 -0
  66. /package/{lib → src}/assets/fonts/Effra_Std_Md.woff2 +0 -0
  67. /package/{lib → src}/assets/fonts/Effra_Std_Rg.eot +0 -0
  68. /package/{lib → src}/assets/fonts/Effra_Std_Rg.ttf +0 -0
  69. /package/{lib → src}/assets/fonts/Effra_Std_Rg.woff +0 -0
  70. /package/{lib → src}/assets/fonts/Effra_Std_Rg.woff2 +0 -0
  71. /package/{lib → src}/assets/fonts/Poppins-Black.ttf +0 -0
  72. /package/{lib → src}/assets/fonts/Poppins-BlackItalic.ttf +0 -0
  73. /package/{lib → src}/assets/fonts/Poppins-Bold.ttf +0 -0
  74. /package/{lib → src}/assets/fonts/Poppins-BoldItalic.ttf +0 -0
  75. /package/{lib → src}/assets/fonts/Poppins-ExtraBold.ttf +0 -0
  76. /package/{lib → src}/assets/fonts/Poppins-ExtraBoldItalic.ttf +0 -0
  77. /package/{lib → src}/assets/fonts/Poppins-ExtraLight.ttf +0 -0
  78. /package/{lib → src}/assets/fonts/Poppins-ExtraLightItalic.ttf +0 -0
  79. /package/{lib → src}/assets/fonts/Poppins-Italic.ttf +0 -0
  80. /package/{lib → src}/assets/fonts/Poppins-Light.ttf +0 -0
  81. /package/{lib → src}/assets/fonts/Poppins-LightItalic.ttf +0 -0
  82. /package/{lib → src}/assets/fonts/Poppins-Medium.ttf +0 -0
  83. /package/{lib → src}/assets/fonts/Poppins-MediumItalic.ttf +0 -0
  84. /package/{lib → src}/assets/fonts/Poppins-Regular.ttf +0 -0
  85. /package/{lib → src}/assets/fonts/Poppins-SemiBold.ttf +0 -0
  86. /package/{lib → src}/assets/fonts/Poppins-SemiBoldItalic.ttf +0 -0
  87. /package/{lib → src}/assets/fonts/Poppins-Thin.ttf +0 -0
  88. /package/{lib → src}/assets/fonts/Poppins-ThinItalic.ttf +0 -0
  89. /package/{lib → src}/assets/icons/topbar-avatar.png +0 -0
  90. /package/{lib → src}/assets/logo/full-white.png +0 -0
  91. /package/{lib → src}/assets/logo/full.png +0 -0
  92. /package/{lib → src}/assets/logo/mobile-white.png +0 -0
  93. /package/{lib → src}/assets/logo/mobile.png +0 -0
@@ -0,0 +1,12 @@
1
+ export enum CookieNames {
2
+ groupId = 'groupId',
3
+ id = 'id',
4
+ languageCode = 'languageCode',
5
+ portalApiToken = 'portalApiToken',
6
+ portalRefreshToken = 'portalRefreshToken',
7
+ recentlyLoggedIn = 'recentlyLoggedIn',
8
+ shopId = 'shopId',
9
+ tenantId = 'tenantId',
10
+ clientId = 'client_id',
11
+ isSidebarOpen = 'isSidebarOpen',
12
+ }
@@ -0,0 +1,4 @@
1
+ export interface Environment {
2
+ portalUrl: string;
3
+ apiRequestUrl: string;
4
+ }
@@ -0,0 +1,3 @@
1
+ export interface HttpResponse<T> extends Response {
2
+ parsedBody?: T;
3
+ }
@@ -0,0 +1,39 @@
1
+ export interface ModulesInfo {
2
+ modulesDetails: ModuleItem[];
3
+ shops: Shop[];
4
+ flags?: string[];
5
+ }
6
+ export interface ModuleItem {
7
+ id: number;
8
+ authorizationId: string;
9
+ name: string;
10
+ url: string;
11
+ relationName: string;
12
+ displayOrder: number;
13
+ entityAccess: number[]; // Shops
14
+ sidebarEntries: SidebarEntry[];
15
+
16
+ children?: SidebarEntry[]; // Remap sidebarEntries to children to allow for recursive fns
17
+ icon?: string;
18
+ translatedName?: string;
19
+ visible?: boolean; // to control visibility of sidebar items
20
+ active?: boolean; // to control active state on page load
21
+ }
22
+
23
+ export interface SidebarEntry {
24
+ moduleAuthorizationId: string;
25
+ authorizationId?: string;
26
+ name: string;
27
+ url: string;
28
+ children: SidebarEntry[];
29
+
30
+ icon?: string;
31
+ translatedName?: string;
32
+ visible?: boolean; // to control visibility of sidebar items
33
+ active?: boolean; // to control active state on page load
34
+ }
35
+
36
+ export interface Shop {
37
+ id: number;
38
+ description: string;
39
+ }
@@ -0,0 +1,20 @@
1
+ <div id="root">
2
+ <div id="main">
3
+ <sidebar [environment]="environment" (selectedRoute)="routeSelected($event)"></sidebar>
4
+ <topbar [environment]="environment"></topbar>
5
+ @if (!useCustomPage) {
6
+ <div class="page-container">
7
+ <div class="page-body">
8
+ <div class="content">
9
+ <slot></slot>
10
+ </div>
11
+ </div>
12
+ </div>
13
+ }
14
+ @if (useCustomPage) {
15
+ <div class="content">
16
+ <slot></slot>
17
+ </div>
18
+ }
19
+ </div>
20
+ </div>
@@ -0,0 +1,30 @@
1
+ @import '../styles/app.scss';
2
+
3
+ #root {
4
+ display: flex;
5
+ flex-flow: column;
6
+ }
7
+
8
+ #main {
9
+ display: flex;
10
+ height: 100%;
11
+ }
12
+
13
+ .page-container {
14
+ font-family: 'Effra';
15
+ padding: 0 24px;
16
+ margin: 0 auto;
17
+ }
18
+
19
+ .page-body {
20
+ padding-top: 25px;
21
+ }
22
+
23
+ .content {
24
+ margin-top: 90px;
25
+ flex-grow: 1;
26
+
27
+ @include responsive(mobileonly) {
28
+ padding: 0;
29
+ }
30
+ }
@@ -0,0 +1,26 @@
1
+ import {
2
+ Component,
3
+ EventEmitter,
4
+ Input,
5
+ Output,
6
+ ViewEncapsulation,
7
+ } from '@angular/core';
8
+ import { Environment } from './models/environment';
9
+ import { SidebarEntry } from './models/module';
10
+
11
+ @Component({
12
+ selector: 'sidebar-navigation',
13
+ templateUrl: './navigation-lib.component.html',
14
+ styleUrls: ['./navigation-lib.component.scss'],
15
+ encapsulation: ViewEncapsulation.ShadowDom,
16
+ standalone: false
17
+ })
18
+ export class NavigationLibComponent {
19
+ @Output() selectedRoute: EventEmitter<SidebarEntry> = new EventEmitter();
20
+ @Input() useCustomPage = false;
21
+ @Input() environment: Environment = <Environment>{};
22
+
23
+ public routeSelected(selectedItem): void {
24
+ this.selectedRoute.emit(selectedItem);
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
4
+ import { NgxSmartModalModule } from 'ngx-smart-modal';
5
+
6
+ import { NavigationLibComponent } from './navigation-lib.component';
7
+ import { SidebarComponent } from './sidebar/sidebar.component';
8
+ import { TopbarComponent } from './topbar/topbar.component';
9
+ import { SubItemComponent } from './sidebar/sub-item/sub-item.component';
10
+ import { SubItemService } from './services/sub-item.service';
11
+ import { ShopModalComponent } from './sidebar/shop-modal/shop-modal.component';
12
+
13
+ import { ModuleService } from './services/module.service';
14
+ import { AuthService } from './services/auth.service';
15
+ import { CookieService } from './services/cookie.service';
16
+
17
+ @NgModule({ declarations: [
18
+ SidebarComponent,
19
+ SubItemComponent,
20
+ TopbarComponent,
21
+ NavigationLibComponent,
22
+ ShopModalComponent,
23
+ ],
24
+ exports: [NavigationLibComponent], imports: [CommonModule,
25
+ NgxSmartModalModule.forRoot()], providers: [SubItemService, ModuleService, AuthService, CookieService, provideHttpClient(withInterceptorsFromDi())] })
26
+ export class NavigationLibModule {}
@@ -0,0 +1,9 @@
1
+ import { Injectable } from '@angular/core';
2
+
3
+ @Injectable({
4
+ providedIn: 'root'
5
+ })
6
+ export class NavigationLibService {
7
+
8
+ constructor() { }
9
+ }
@@ -0,0 +1,39 @@
1
+ import { Inject, Injectable, DOCUMENT } from '@angular/core';
2
+
3
+
4
+ // @dynamic
5
+ @Injectable({
6
+ providedIn: 'root',
7
+ })
8
+ export class AuthService {
9
+ environmentUrl: string;
10
+ _refreshTokenStore: string;
11
+ _tokenStore: string;
12
+
13
+ constructor(@Inject(DOCUMENT) private document: Document) {}
14
+
15
+ public logout(): void {
16
+ this.clearCookies();
17
+ }
18
+
19
+ clearCookies(): void {
20
+ let cookies = document.cookie.split('; ');
21
+ for (let c = 0; c < cookies.length; c++) {
22
+ let domain = window.location.hostname.split('.');
23
+ while (domain.length > 0) {
24
+ let cookieBase =
25
+ encodeURIComponent(cookies[c].split(';')[0].split('=')[0]) +
26
+ '=; expires=Thu, 01-Jan-1970 00:00:01 GMT; domain=' +
27
+ domain.join('.') +
28
+ ' ;path=';
29
+ let p = location.pathname.split('/');
30
+ document.cookie = cookieBase + '/';
31
+ while (p.length > 0) {
32
+ document.cookie = cookieBase + p.join('/');
33
+ p.pop();
34
+ }
35
+ domain.shift();
36
+ }
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,33 @@
1
+ import { Injectable, Inject, DOCUMENT } from '@angular/core';
2
+
3
+ import {
4
+ set as setCookie,
5
+ get as getCookie,
6
+ remove as removeCookie,
7
+ } from 'es-cookie';
8
+ import { CookieNames } from '../models/cookie-names';
9
+ import { getDomain } from 'tldjs';
10
+
11
+ // @dynamic
12
+ @Injectable()
13
+ export class CookieService {
14
+ constructor(@Inject(DOCUMENT) private document: Document) {}
15
+
16
+ private getDomain(): string {
17
+ let tld = getDomain(this.document.location.href);
18
+ tld = tld != null ? '.' + tld : '';
19
+ return tld;
20
+ }
21
+
22
+ getCookie(cookieName: CookieNames): string | undefined {
23
+ return getCookie(cookieName);
24
+ }
25
+
26
+ setCookie(cookieName: CookieNames, value: any): void {
27
+ setCookie(cookieName, value, { path: '/', domain: this.getDomain(), sameSite : 'none' , secure : true });
28
+ }
29
+
30
+ removeCookie(cookieName: CookieNames): void {
31
+ removeCookie(cookieName, { path: '/', domain: this.getDomain() });
32
+ }
33
+ }
@@ -0,0 +1,26 @@
1
+ import { HttpResponse } from '../models/http-response';
2
+
3
+ export async function httpGet<T>(
4
+ requestUrl: string,
5
+ tokenIn: string
6
+ ): Promise<HttpResponse<T>> {
7
+ const token = getCookieValue('portalApiToken');
8
+ const response: HttpResponse<T> = await fetch(requestUrl, {
9
+ method: 'get',
10
+ headers: new Headers({
11
+ Authorization: 'Bearer ' + token,
12
+ 'Content-Type': 'application/x-www-form-urlencoded',
13
+ }),
14
+ });
15
+ response.parsedBody = response.ok
16
+ ? (response.parsedBody = await response.json())
17
+ : [];
18
+ return response;
19
+ }
20
+
21
+ function getCookieValue(cookieName: string) {
22
+ const b = document.cookie.match(
23
+ '(^|[^;]+)\\s*' + cookieName + '\\s*=\\s*([^;]+)'
24
+ );
25
+ return b ? b.pop() : '';
26
+ }
@@ -0,0 +1,153 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { ModuleItem, ModulesInfo, SidebarEntry } from '../models/module';
3
+ import { httpGet } from './http.service';
4
+ import { Subject } from 'rxjs';
5
+ import { testModuleDetails } from '../data/test-data';
6
+
7
+ @Injectable({
8
+ providedIn: 'root',
9
+ })
10
+ export class ModuleService {
11
+ storedModuleFlags: string[] = [];
12
+ storedModuleFlagsChange: Subject<string[]> = new Subject<string[]>();
13
+ currentRouteName: Subject<string> = new Subject<string>();
14
+ currentModule;
15
+ modules: ModuleItem[];
16
+
17
+ constructor() {
18
+ this.storedModuleFlagsChange.subscribe((value) => {
19
+ this.storedModuleFlags = value;
20
+ });
21
+ }
22
+
23
+ public async getUserModules(
24
+ gatewayUrl: string,
25
+ token: string
26
+ ): Promise<ModulesInfo> {
27
+ if (gatewayUrl === 'TEST') {
28
+ this.storedModuleFlagsChange.next(testModuleDetails.flags);
29
+ return Promise.resolve(testModuleDetails);
30
+ }
31
+
32
+ const requestUrl = gatewayUrl + '/Module';
33
+ const response = await httpGet<ModulesInfo>(requestUrl, token);
34
+ this.storedModuleFlagsChange.next(response.parsedBody.flags);
35
+ return Promise.resolve(response.parsedBody);
36
+ }
37
+
38
+ public setNavigationTitle(selectedItem?: ModuleItem | SidebarEntry) {
39
+ var pathArray = window.location.pathname.split('/');
40
+ let translatedModuleName = '';
41
+ let item = selectedItem;
42
+ if (pathArray.length > 1 && !item) {
43
+ let currentPath = window.location.pathname;
44
+ item = this.findNestedUrlMatch(this.modules, currentPath);
45
+ item
46
+ ? (translatedModuleName = item.translatedName || item.name)
47
+ : (translatedModuleName = pathArray[pathArray.length - 1]);
48
+ } else if (item) {
49
+ translatedModuleName = item.translatedName || item.name;
50
+ }
51
+ translatedModuleName === 'main' ? (translatedModuleName = '') : '';
52
+ this.currentRouteName.next(translatedModuleName);
53
+
54
+ if (item) {
55
+ this.setItemVisbility(item);
56
+ } else {
57
+ this.currentModule = this.modules.find(
58
+ (m) =>
59
+ m.url.toLowerCase().replace(/\//g, '') ===
60
+ pathArray[1].toLowerCase().replace(/\//g, '')
61
+ );
62
+ }
63
+ }
64
+
65
+ public findNestedUrlMatch(subItems, urlPath: string) {
66
+ let nameMatch;
67
+ subItems.forEach((i: ModuleItem | SidebarEntry) => {
68
+ if (
69
+ i.url &&
70
+ i.url.replace(/\//g, '') &&
71
+ i.url.toLowerCase().replace(/\//g, '') ===
72
+ urlPath.toLowerCase().replace(/\//g, '')
73
+ ) {
74
+ nameMatch = i;
75
+ i.active = true;
76
+ } else {
77
+ i.active = false;
78
+ const childMatch = this.findNestedUrlMatch(i.children, urlPath);
79
+ if (childMatch) {
80
+ nameMatch = childMatch;
81
+ }
82
+ }
83
+ });
84
+
85
+ return nameMatch;
86
+ }
87
+
88
+ public setItemVisbility(item): void {
89
+ let activeModule: ModuleItem = this.modules.find(
90
+ (m: ModuleItem) =>
91
+ m.authorizationId ===
92
+ (item.authorizationId || item.moduleAuthorizationId)
93
+ );
94
+
95
+ this.currentModule ? '' : (this.currentModule = activeModule);
96
+ activeModule.children.map((se: SidebarEntry) => {
97
+ se.visible = true;
98
+ if (item.name !== se.name) {
99
+ let isCurrent = se.children.some((sec) => sec.name === item.name);
100
+ se.children.map((sec) => {
101
+ sec.visible = isCurrent;
102
+ });
103
+ }
104
+ });
105
+ }
106
+
107
+ public mapModuleItems(modules: ModuleItem[]): ModuleItem[] {
108
+ modules.sort(
109
+ (a, b) => a.displayOrder - b.displayOrder || a.name.localeCompare(b.name)
110
+ ); // Alphabetically if modules share a displayOrder
111
+ const isSupportAdmin = this.storedModuleFlags.includes('SupportAdmin');
112
+ const isTenantAdmin = this.storedModuleFlags.includes('TenantAdmin');
113
+ let portalModule = modules.find((m) => m.authorizationId === 'portal');
114
+ isTenantAdmin ? '' : (modules = modules.filter((m) => m !== portalModule));
115
+
116
+ // modules = this.mapAdminItems(modules, isSupportAdmin); // Pending support for individual item flags
117
+ this.modules = modules.map((m) => {
118
+ if (m && m.sidebarEntries) {
119
+ m.children = JSON.parse(JSON.stringify(m.sidebarEntries));
120
+ m.sidebarEntries = [];
121
+ } else {
122
+ m.children = [];
123
+ }
124
+ m.visible = true;
125
+ m.sidebarEntries ? `` : (m.children = []);
126
+ m.url ? `` : (m.url = '/');
127
+ return m;
128
+ });
129
+ this.setNavigationTitle();
130
+
131
+ return this.modules;
132
+ }
133
+
134
+ public mapAdminItems(modules, isSupportAdmin: boolean): ModuleItem[] {
135
+ modules.forEach((m) => {
136
+ if (!isSupportAdmin && m.isAdminOnly) {
137
+ modules = modules.filter((i) => i.name !== m.name);
138
+ }
139
+ if (m.children) {
140
+ m.children = this.mapAdminItems(m.children, isSupportAdmin);
141
+ }
142
+ });
143
+
144
+ return modules;
145
+ }
146
+
147
+ public getCurrentModule(selectedItem: ModuleItem | SidebarEntry): ModuleItem {
148
+ var moduleRoute = window.location.pathname.split('/')[1];
149
+ if (!moduleRoute) return null;
150
+ this.setNavigationTitle(selectedItem);
151
+ return this.modules.filter(m => m.url.indexOf(moduleRoute) > 0)[0];
152
+ }
153
+ }
@@ -0,0 +1,56 @@
1
+ import { EventEmitter, Injectable } from '@angular/core';
2
+ import { ModuleItem } from '../models/module';
3
+ import { getDomain } from 'tldjs';
4
+ import { set as setCookie } from 'es-cookie';
5
+
6
+ @Injectable()
7
+ export class SubItemService {
8
+ subItemClickEvent = new EventEmitter();
9
+ _storage: Storage;
10
+
11
+ constructor() {
12
+ this._storage = window.localStorage;
13
+ }
14
+
15
+ public saveItemInfo(selectedModule: ModuleItem, shopId: number): void {
16
+ let moduleKey = `context${selectedModule.relationName}Id`;
17
+ let entity: any = selectedModule;
18
+
19
+ // The POS module is a special case and we dont want to change the shopId context when
20
+ // changing for the other modules. We only change the shopId context for the POS when
21
+ // explicitly clicking a new shopId on the POS module.
22
+ // For legacy reasons the POS uses the generic key 'shopId' and modules use 'contextShopId'.
23
+ if (selectedModule.authorizationId === 'pos') {
24
+ moduleKey = 'shopId';
25
+ entity = shopId;
26
+ }
27
+
28
+ if (
29
+ (selectedModule.authorizationId !== 'pos' &&
30
+ selectedModule.entityAccess.length > 0) ||
31
+ selectedModule.name === 'orb'
32
+ ) {
33
+ entity = shopId;
34
+ }
35
+
36
+ let tld = getDomain(window.location.href);
37
+ tld = tld !== null ? `.${tld}` : '';
38
+
39
+ // ? Sets the new data into storage, with a formatted domain
40
+ this._storage.setItem(moduleKey, entity.toString());
41
+
42
+ // ? TEMP: Sets the new cookie, with a formatted domain
43
+ setCookie(moduleKey, entity.toString(), {
44
+ path: '/',
45
+ domain: tld,
46
+ sameSite : 'none',
47
+ secure : true
48
+ });
49
+ }
50
+
51
+ subItemclicked(item) {
52
+ this.subItemClickEvent.emit(item);
53
+ }
54
+ }
55
+
56
+ // This service exists to allow the EventEmitter to bubble up from the recursive subItem component to the parent sidebar component.
@@ -0,0 +1,17 @@
1
+ <ngx-smart-modal #shopModal identifier="shopModal">
2
+ <h1 class="shopSelect">Shop Select</h1>
3
+ @if (availableShops) {
4
+ @if (availableShops.length > 8 || copyAvailableShops.length > 8) {
5
+ <input class="shopSearchInput" type="search" autocomplete="off" id="search" [value]="shopSearchValue"
6
+ required="false" [placeholder]="shopSearchPlaceholder" (keyup)="handleShopSearch($event)"
7
+ (click)="handleShopSearch($event)" />
8
+ }
9
+ <div class="shopButtons">
10
+ @for (shop of availableShops; track shop) {
11
+ <button class="shopButton"
12
+ [attr.data-testid]="'nav-shop' + shop.id"
13
+ (click)="shopSelected(shop)">{{shop.description}}</button>
14
+ }
15
+ </div>
16
+ }
17
+ </ngx-smart-modal>
@@ -0,0 +1,73 @@
1
+ @import "../../../styles/app.scss";
2
+
3
+ @font-face {
4
+ font-family: 'Poppins';
5
+ src: url('../../../assets/fonts/Poppins-Regular.ttf') format('truetype');
6
+ font-weight: 400;
7
+ font-style: normal;
8
+ }
9
+
10
+ @font-face {
11
+ font-family: 'Poppins';
12
+ src: url('../../../assets/fonts/Poppins-Bold.ttf') format('truetype');
13
+ font-weight: 700;
14
+ font-style: normal;
15
+ }
16
+
17
+ @font-face {
18
+ font-family: 'Poppins';
19
+ src: url('../../../assets/fonts/Poppins-Italic.ttf') format('truetype');
20
+ font-weight: 400;
21
+ font-style: italic;
22
+ }
23
+
24
+ @font-face {
25
+ font-family: 'Poppins';
26
+ src: url('../../../assets/fonts/Poppins-SemiBold.ttf') format('truetype');
27
+ font-weight: 600;
28
+ font-style: normal;
29
+ }
30
+
31
+ #shopModal {
32
+ direction: ltr;
33
+ }
34
+
35
+ .shopButtons {
36
+ display: block;
37
+ padding: 10px 0px;
38
+ margin: auto;
39
+ width: 100%;
40
+ text-align: center;
41
+ }
42
+
43
+ .shopButton {
44
+ cursor: pointer;
45
+ min-width: 90px;
46
+ border-radius: 5px;
47
+ border: none;
48
+ outline: none;
49
+ padding: 5px 10px;
50
+ }
51
+
52
+ .shopButton + .shopButton {
53
+ margin-left: 10px;
54
+ margin-bottom: 10px;
55
+ }
56
+
57
+ .shopSearchInput {
58
+ display: block;
59
+ direction: ltr;
60
+ width: 90%;
61
+ height: inherit;
62
+ margin: auto;
63
+ }
64
+
65
+ input:invalid {
66
+ box-shadow: none; /* prevent Mozilla Firefox from creating a red border */
67
+ }
68
+
69
+ .shopSelect {
70
+ font-size: 18px;
71
+ font-family: "Poppins", sans-serif;
72
+ text-align: center;
73
+ }
@@ -0,0 +1,36 @@
1
+ import { Component, EventEmitter, Input, Output } from '@angular/core';
2
+ import { NgxSmartModalService } from 'ngx-smart-modal';
3
+ import { Shop } from '../../models/module';
4
+
5
+ @Component({
6
+ selector: 'shop-modal',
7
+ templateUrl: './shop-modal.component.html',
8
+ styleUrls: ['./shop-modal.component.scss'],
9
+ standalone: false
10
+ })
11
+ export class ShopModalComponent {
12
+ @Input() availableShops: Shop[] = [];
13
+ copyAvailableShops: Shop[] = [];
14
+ @Output() selectedShop: EventEmitter<Shop> = new EventEmitter();
15
+ @Input() shopSearchValue: any = '';
16
+ shopSearchPlaceholder = 'Search for a Shop ...';
17
+
18
+ constructor(public ngxSmartModalService: NgxSmartModalService) {}
19
+
20
+ shopSelected(shop): void {
21
+ this.selectedShop.emit(shop);
22
+ this.ngxSmartModalService.getModal('shopModal').close();
23
+ }
24
+
25
+ handleShopSearch(e): void {
26
+ if (this.copyAvailableShops.length < 1) {
27
+ this.copyAvailableShops = [...this.availableShops];
28
+ }
29
+ this.shopSearchValue = e && e.target ? e.target.value : '';
30
+ this.availableShops = this.copyAvailableShops.filter((shop) =>
31
+ shop.description
32
+ .toLowerCase()
33
+ .includes(this.shopSearchValue.toLowerCase())
34
+ );
35
+ }
36
+ }