@transcommerce/cwm-shared 1.1.87 → 1.1.90

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 (31) hide show
  1. package/fesm2022/transcommerce-cwm-shared.mjs +1462 -444
  2. package/fesm2022/transcommerce-cwm-shared.mjs.map +1 -1
  3. package/lib/components/external-navigation/external-navigation.component.d.ts +1 -1
  4. package/lib/components/navigate-to-route/navigate-to-route.component.d.ts +1 -1
  5. package/lib/components/page-not-found/page-not-found.component.d.ts +1 -1
  6. package/lib/cwm-shared-standalone.d.ts +19 -0
  7. package/lib/helpers/async-utils.d.ts +31 -3
  8. package/lib/helpers/jwt.d.ts +35 -0
  9. package/lib/helpers/time-span.d.ts +344 -0
  10. package/lib/helpers/utilities.d.ts +301 -0
  11. package/lib/models/slide.d.ts +43 -0
  12. package/lib/models/style/color-style.d.ts +2 -2
  13. package/lib/models/style/coordinates-style.d.ts +21 -2
  14. package/lib/models/style/font-style.d.ts +39 -2
  15. package/lib/models/style/hex-color.d.ts +8 -0
  16. package/lib/models/style/image-style.d.ts +57 -3
  17. package/lib/models/style/justifications.d.ts +8 -0
  18. package/lib/models/style/named-colors.d.ts +8 -0
  19. package/lib/models/style/ng-style-attribute.d.ts +14 -0
  20. package/lib/models/style/on-style.d.ts +2 -2
  21. package/lib/models/style/rgba-color-style.d.ts +42 -2
  22. package/lib/models/style/size-style.d.ts +20 -3
  23. package/lib/models/style/text-style.d.ts +65 -2
  24. package/lib/modules/cwm-shared.module.d.ts +25 -10
  25. package/lib/pipes/safe-html.pipe.d.ts +1 -1
  26. package/lib/services/local-storage.service.d.ts +2 -0
  27. package/package.json +1 -1
  28. package/public-api.d.ts +10 -10
  29. package/lib/helpers/db-keys.d.ts +0 -18
  30. package/lib/helpers/time-span-overflow-error.d.ts +0 -2
  31. package/lib/models/style/ng-style-item.d.ts +0 -3
@@ -1,21 +1,21 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Component, Injectable, InjectionToken, Pipe, NgModule, Inject } from '@angular/core';
3
- import * as i1 from '@angular/router';
4
- import { RouterModule } from '@angular/router';
2
+ import { Component, Injectable, InjectionToken, Pipe, Inject, NgModule } from '@angular/core';
5
3
  import * as i2 from '@angular/forms';
6
4
  import { FormsModule } from '@angular/forms';
5
+ import * as i1 from '@angular/router';
6
+ import { RouterModule } from '@angular/router';
7
7
  import { Subject, firstValueFrom } from 'rxjs';
8
- import { InteractionType, PublicClientApplication, LogLevel } from '@azure/msal-browser';
8
+ import { PublicClientApplication, LogLevel, InteractionType } from '@azure/msal-browser';
9
9
  import * as i2$1 from '@angular/common/http';
10
10
  import { HttpResponseBase, HttpResponse, HttpErrorResponse, HttpHeaders, HttpStatusCode } from '@angular/common/http';
11
- import { CommonModule, DOCUMENT } from '@angular/common';
12
- export * from '@angular/common';
13
- import { MsalModule } from '@azure/msal-angular';
14
- export * from '@azure/msal-angular';
15
11
  import * as i1$1 from '@angular/platform-browser';
16
12
  import { BrowserModule } from '@angular/platform-browser';
13
+ import { DOCUMENT, CommonModule } from '@angular/common';
14
+ export * from '@angular/common';
17
15
  import { AppConfigurationClient } from '@azure/app-configuration';
18
16
  import { keys, countBy } from 'lodash';
17
+ import { MsalModule } from '@azure/msal-angular';
18
+ export * from '@azure/msal-angular';
19
19
 
20
20
  class ExternalNavigationComponent {
21
21
  router;
@@ -49,7 +49,7 @@ class ExternalNavigationComponent {
49
49
  }
50
50
  }
51
51
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: ExternalNavigationComponent, deps: [{ token: i1.Router }], target: i0.ɵɵFactoryTarget.Component });
52
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.19", type: ExternalNavigationComponent, isStandalone: false, selector: "cwm-external-navigation", ngImport: i0, template: `<div class="container">
52
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.19", type: ExternalNavigationComponent, isStandalone: true, selector: "cwm-external-navigation", ngImport: i0, template: `<div class="container">
53
53
  <h3>Enter the destination route, URL, or Href you want and press the button that uses the navigation method of your choosing:</h3>
54
54
  <input id="txtRoute" type="text" [(ngModel)]="route" placeholder="Enter the destination route, URL, or Href..."
55
55
  (keydown)="onKeyPress($event)"/>
@@ -58,13 +58,14 @@ class ExternalNavigationComponent {
58
58
  <button (click)="navigateByUrl()">Navigate by URL</button>
59
59
  <button (click)="navigateToExternalUrl()">Navigate to external URL</button>
60
60
  <button (click)="navigateToExternalHref()">Navigate to external Href</button>
61
- </div>`, isInline: true, dependencies: [{ kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
61
+ </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
62
62
  }
63
63
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: ExternalNavigationComponent, decorators: [{
64
64
  type: Component,
65
65
  args: [{
66
66
  selector: 'cwm-external-navigation',
67
- standalone: false,
67
+ standalone: true,
68
+ imports: [FormsModule],
68
69
  template: `<div class="container">
69
70
  <h3>Enter the destination route, URL, or Href you want and press the button that uses the navigation method of your choosing:</h3>
70
71
  <input id="txtRoute" type="text" [(ngModel)]="route" placeholder="Enter the destination route, URL, or Href..."
@@ -98,11 +99,11 @@ class NavigateToRouteComponent {
98
99
  }
99
100
  }
100
101
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: NavigateToRouteComponent, deps: [{ token: i1.Router }], target: i0.ɵɵFactoryTarget.Component });
101
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.19", type: NavigateToRouteComponent, isStandalone: false, selector: "cwm-navigate-to-route", ngImport: i0, template: "<div class=\"container\">\n <h2>Enter the route you want to navigate to:</h2>\n <input id=\"txtRoute\" type=\"text\" [(ngModel)]=\"route\" placeholder=\"Enter Route...\"\n (keydown)=\"onKeyPress($event)\"/>\n <button (click)=\"navigateToRoute()\">Navigate</button>\n</div>\n``\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
102
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.19", type: NavigateToRouteComponent, isStandalone: true, selector: "cwm-navigate-to-route", ngImport: i0, template: "<div class=\"container\">\n <h2>Enter the route you want to navigate to:</h2>\n <input id=\"txtRoute\" type=\"text\" [(ngModel)]=\"route\" placeholder=\"Enter Route...\"\n (keydown)=\"onKeyPress($event)\"/>\n <button (click)=\"navigateToRoute()\">Navigate</button>\n</div>\n``\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
102
103
  }
103
104
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: NavigateToRouteComponent, decorators: [{
104
105
  type: Component,
105
- args: [{ selector: 'cwm-navigate-to-route', standalone: false, template: "<div class=\"container\">\n <h2>Enter the route you want to navigate to:</h2>\n <input id=\"txtRoute\" type=\"text\" [(ngModel)]=\"route\" placeholder=\"Enter Route...\"\n (keydown)=\"onKeyPress($event)\"/>\n <button (click)=\"navigateToRoute()\">Navigate</button>\n</div>\n``\n", styles: [":host{display:block}\n"] }]
106
+ args: [{ selector: 'cwm-navigate-to-route', standalone: true, imports: [FormsModule], template: "<div class=\"container\">\n <h2>Enter the route you want to navigate to:</h2>\n <input id=\"txtRoute\" type=\"text\" [(ngModel)]=\"route\" placeholder=\"Enter Route...\"\n (keydown)=\"onKeyPress($event)\"/>\n <button (click)=\"navigateToRoute()\">Navigate</button>\n</div>\n``\n", styles: [":host{display:block}\n"] }]
106
107
  }], ctorParameters: () => [{ type: i1.Router }] });
107
108
 
108
109
  class PageNotFoundComponent {
@@ -135,11 +136,11 @@ class PageNotFoundComponent {
135
136
  }, 1000);
136
137
  }
137
138
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: PageNotFoundComponent, deps: [{ token: i1.Router }], target: i0.ɵɵFactoryTarget.Component });
138
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.19", type: PageNotFoundComponent, isStandalone: false, selector: "cwm-page-not-found", ngImport: i0, template: "<div class=\"not-found-container\">\n <div class=\"not-found-card\">\n <div class=\"emoji-icon\">\uD83D\uDD0E</div>\n <div class=\"error-code\">404</div>\n <h1 class=\"error-title\">Page Not Found</h1>\n <p class=\"error-description\">\n The page you're looking for doesn't exist or has been moved. Let's get you back on track!\n </p>\n\n <div class=\"button-group\">\n <a routerLink=\"/\" class=\"btn btn-primary\">\uD83C\uDFE0 Go to Home</a>\n <a routerLink=\"/dashboard\" class=\"btn btn-secondary\">\uD83D\uDCCA Dashboard</a>\n <a href=\"javascript:history.back()\" class=\"btn btn-outline\">\u2190 Go Back</a>\n <a href=\"mailto:support@cheapweedmenu.com?subject=Broken%20link%20-%20404&body=I%20found%20a%20broken%20link\" class=\"btn btn-ghost\">\uD83D\uDCE7 Report Issue</a>\n </div>\n\n <div class=\"redirect-info\">\n <small class=\"redirect-timer\">You will be redirected to home in <span class=\"timer-badge\">{{ redirectCountdown }}</span> seconds</small>\n <div class=\"breadcrumb\">\n Requested URL: <code>{{ requestedUrl }}</code>\n </div>\n </div>\n </div>\n</div>\n", styles: [".menu{color:#fff}.menu-bg{background-color:#333}.hybrid{color:#375b2b}.hybrid-bg{background-color:#375b2b}.shake{color:#4e544c}.shake-bg{background-color:#4e544c}.indica{color:#1c2e16}.indica-bg{background-color:#1c2e16}.sativa{color:#d7d0c4}.sativa-bg{background-color:#d7d0c4}@keyframes float{0%,to{transform:translateY(0)}50%{transform:translateY(-20px)}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.not-found-container{min-height:70vh;display:flex;align-items:center;justify-content:center;padding:24px;background:linear-gradient(135deg,#1c2e16,#375b2b)}.not-found-card{max-width:720px;width:100%;text-align:center;border-radius:12px;box-shadow:0 8px 30px #0000004d;padding:36px;background:linear-gradient(180deg,#d7d0c4,#e8e2d8);animation:fadeInUp .6s ease-out}.emoji-icon{font-size:80px;line-height:1;margin-bottom:16px;animation:float 3s ease-in-out infinite;display:inline-block}.error-code{margin:0;font-size:48px;font-weight:700;color:#1c2e16;letter-spacing:-1px}.error-title{margin:8px 0;font-size:28px;font-weight:600;color:#1c2e16}.error-description{color:#4e544c;margin:12px 0 28px;line-height:1.6;font-size:16px}.button-group{display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin:32px 0}.btn{padding:12px 24px;border-radius:8px;text-decoration:none;font-weight:600;font-size:14px;transition:all .5s ease;cursor:pointer;border:none;display:inline-block}.btn-primary{background-color:#1c2e16;color:#d7d0c4;transition-property:all;transition-duration:.5s}.btn-primary:hover{background-color:#160816;color:#d7d0c4;transform:translateY(-2px);box-shadow:0 4px 12px #1c2e1666}.btn-secondary{background-color:#375b2b;color:#d7d0c4;transition-property:all;transition-duration:.5s}.btn-secondary:hover{background-color:#1c2e16;color:#d7d0c4;transform:translateY(-2px)}.btn-outline{background-color:#d7d0c4;color:#1c2e16;transition-property:all;transition-duration:.5s}.btn-outline:hover{background-color:#1c2e16;color:#d7d0c4;transform:translateY(-2px)}.btn-ghost{background:transparent;border:2px solid #1c2e16;color:#1c2e16;transition-property:all;transition-duration:.5s}.btn-ghost:hover{border-color:#375b2b;background-color:#375b2b;color:#d7d0c4;transform:translateY(-2px)}.redirect-info{margin-top:24px;padding-top:24px;border-top:2px solid #1c2e16}.redirect-timer{font-size:14px;color:#4e544c;margin-top:12px}.timer-badge{display:inline-block;background-color:#1c2e16;color:#d7d0c4;padding:4px 10px;border-radius:4px;font-weight:600;margin-left:4px}.breadcrumb{margin-top:20px;font-size:12px;color:#4e544c}.breadcrumb code{background-color:#1c2e16;color:#d7d0c4;padding:2px 6px;border-radius:4px}\n"], dependencies: [{ kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
139
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.19", type: PageNotFoundComponent, isStandalone: true, selector: "cwm-page-not-found", ngImport: i0, template: "<div class=\"not-found-container\">\n <div class=\"not-found-card\">\n <div class=\"emoji-icon\">\uD83D\uDD0E</div>\n <div class=\"error-code\">404</div>\n <h1 class=\"error-title\">Page Not Found</h1>\n <p class=\"error-description\">\n The page you're looking for doesn't exist or has been moved. Let's get you back on track!\n </p>\n\n <div class=\"button-group\">\n <a routerLink=\"/\" class=\"btn btn-primary\">\uD83C\uDFE0 Go to Home</a>\n <a routerLink=\"/dashboard\" class=\"btn btn-secondary\">\uD83D\uDCCA Dashboard</a>\n <a href=\"javascript:history.back()\" class=\"btn btn-outline\">\u2190 Go Back</a>\n <a href=\"mailto:support@cheapweedmenu.com?subject=Broken%20link%20-%20404&body=I%20found%20a%20broken%20link\" class=\"btn btn-ghost\">\uD83D\uDCE7 Report Issue</a>\n </div>\n\n <div class=\"redirect-info\">\n <small class=\"redirect-timer\">You will be redirected to home in <span class=\"timer-badge\">{{ redirectCountdown }}</span> seconds</small>\n <div class=\"breadcrumb\">\n Requested URL: <code>{{ requestedUrl }}</code>\n </div>\n </div>\n </div>\n</div>\n", styles: [".menu{color:#fff}.menu-bg{background-color:#333}.hybrid{color:#375b2b}.hybrid-bg{background-color:#375b2b}.shake{color:#4e544c}.shake-bg{background-color:#4e544c}.indica{color:#1c2e16}.indica-bg{background-color:#1c2e16}.sativa{color:#d7d0c4}.sativa-bg{background-color:#d7d0c4}@keyframes float{0%,to{transform:translateY(0)}50%{transform:translateY(-20px)}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.not-found-container{min-height:70vh;display:flex;align-items:center;justify-content:center;padding:24px;background:linear-gradient(135deg,#1c2e16,#375b2b)}.not-found-card{max-width:720px;width:100%;text-align:center;border-radius:12px;box-shadow:0 8px 30px #0000004d;padding:36px;background:linear-gradient(180deg,#d7d0c4,#e8e2d8);animation:fadeInUp .6s ease-out}.emoji-icon{font-size:80px;line-height:1;margin-bottom:16px;animation:float 3s ease-in-out infinite;display:inline-block}.error-code{margin:0;font-size:48px;font-weight:700;color:#1c2e16;letter-spacing:-1px}.error-title{margin:8px 0;font-size:28px;font-weight:600;color:#1c2e16}.error-description{color:#4e544c;margin:12px 0 28px;line-height:1.6;font-size:16px}.button-group{display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin:32px 0}.btn{padding:12px 24px;border-radius:8px;text-decoration:none;font-weight:600;font-size:14px;transition:all .5s ease;cursor:pointer;border:none;display:inline-block}.btn-primary{background-color:#1c2e16;color:#d7d0c4;transition-property:all;transition-duration:.5s}.btn-primary:hover{background-color:#160816;color:#d7d0c4;transform:translateY(-2px);box-shadow:0 4px 12px #1c2e1666}.btn-secondary{background-color:#375b2b;color:#d7d0c4;transition-property:all;transition-duration:.5s}.btn-secondary:hover{background-color:#1c2e16;color:#d7d0c4;transform:translateY(-2px)}.btn-outline{background-color:#d7d0c4;color:#1c2e16;transition-property:all;transition-duration:.5s}.btn-outline:hover{background-color:#1c2e16;color:#d7d0c4;transform:translateY(-2px)}.btn-ghost{background:transparent;border:2px solid #1c2e16;color:#1c2e16;transition-property:all;transition-duration:.5s}.btn-ghost:hover{border-color:#375b2b;background-color:#375b2b;color:#d7d0c4;transform:translateY(-2px)}.redirect-info{margin-top:24px;padding-top:24px;border-top:2px solid #1c2e16}.redirect-timer{font-size:14px;color:#4e544c;margin-top:12px}.timer-badge{display:inline-block;background-color:#1c2e16;color:#d7d0c4;padding:4px 10px;border-radius:4px;font-weight:600;margin-left:4px}.breadcrumb{margin-top:20px;font-size:12px;color:#4e544c}.breadcrumb code{background-color:#1c2e16;color:#d7d0c4;padding:2px 6px;border-radius:4px}\n"] });
139
140
  }
140
141
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: PageNotFoundComponent, decorators: [{
141
142
  type: Component,
142
- args: [{ selector: 'cwm-page-not-found', standalone: false, template: "<div class=\"not-found-container\">\n <div class=\"not-found-card\">\n <div class=\"emoji-icon\">\uD83D\uDD0E</div>\n <div class=\"error-code\">404</div>\n <h1 class=\"error-title\">Page Not Found</h1>\n <p class=\"error-description\">\n The page you're looking for doesn't exist or has been moved. Let's get you back on track!\n </p>\n\n <div class=\"button-group\">\n <a routerLink=\"/\" class=\"btn btn-primary\">\uD83C\uDFE0 Go to Home</a>\n <a routerLink=\"/dashboard\" class=\"btn btn-secondary\">\uD83D\uDCCA Dashboard</a>\n <a href=\"javascript:history.back()\" class=\"btn btn-outline\">\u2190 Go Back</a>\n <a href=\"mailto:support@cheapweedmenu.com?subject=Broken%20link%20-%20404&body=I%20found%20a%20broken%20link\" class=\"btn btn-ghost\">\uD83D\uDCE7 Report Issue</a>\n </div>\n\n <div class=\"redirect-info\">\n <small class=\"redirect-timer\">You will be redirected to home in <span class=\"timer-badge\">{{ redirectCountdown }}</span> seconds</small>\n <div class=\"breadcrumb\">\n Requested URL: <code>{{ requestedUrl }}</code>\n </div>\n </div>\n </div>\n</div>\n", styles: [".menu{color:#fff}.menu-bg{background-color:#333}.hybrid{color:#375b2b}.hybrid-bg{background-color:#375b2b}.shake{color:#4e544c}.shake-bg{background-color:#4e544c}.indica{color:#1c2e16}.indica-bg{background-color:#1c2e16}.sativa{color:#d7d0c4}.sativa-bg{background-color:#d7d0c4}@keyframes float{0%,to{transform:translateY(0)}50%{transform:translateY(-20px)}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.not-found-container{min-height:70vh;display:flex;align-items:center;justify-content:center;padding:24px;background:linear-gradient(135deg,#1c2e16,#375b2b)}.not-found-card{max-width:720px;width:100%;text-align:center;border-radius:12px;box-shadow:0 8px 30px #0000004d;padding:36px;background:linear-gradient(180deg,#d7d0c4,#e8e2d8);animation:fadeInUp .6s ease-out}.emoji-icon{font-size:80px;line-height:1;margin-bottom:16px;animation:float 3s ease-in-out infinite;display:inline-block}.error-code{margin:0;font-size:48px;font-weight:700;color:#1c2e16;letter-spacing:-1px}.error-title{margin:8px 0;font-size:28px;font-weight:600;color:#1c2e16}.error-description{color:#4e544c;margin:12px 0 28px;line-height:1.6;font-size:16px}.button-group{display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin:32px 0}.btn{padding:12px 24px;border-radius:8px;text-decoration:none;font-weight:600;font-size:14px;transition:all .5s ease;cursor:pointer;border:none;display:inline-block}.btn-primary{background-color:#1c2e16;color:#d7d0c4;transition-property:all;transition-duration:.5s}.btn-primary:hover{background-color:#160816;color:#d7d0c4;transform:translateY(-2px);box-shadow:0 4px 12px #1c2e1666}.btn-secondary{background-color:#375b2b;color:#d7d0c4;transition-property:all;transition-duration:.5s}.btn-secondary:hover{background-color:#1c2e16;color:#d7d0c4;transform:translateY(-2px)}.btn-outline{background-color:#d7d0c4;color:#1c2e16;transition-property:all;transition-duration:.5s}.btn-outline:hover{background-color:#1c2e16;color:#d7d0c4;transform:translateY(-2px)}.btn-ghost{background:transparent;border:2px solid #1c2e16;color:#1c2e16;transition-property:all;transition-duration:.5s}.btn-ghost:hover{border-color:#375b2b;background-color:#375b2b;color:#d7d0c4;transform:translateY(-2px)}.redirect-info{margin-top:24px;padding-top:24px;border-top:2px solid #1c2e16}.redirect-timer{font-size:14px;color:#4e544c;margin-top:12px}.timer-badge{display:inline-block;background-color:#1c2e16;color:#d7d0c4;padding:4px 10px;border-radius:4px;font-weight:600;margin-left:4px}.breadcrumb{margin-top:20px;font-size:12px;color:#4e544c}.breadcrumb code{background-color:#1c2e16;color:#d7d0c4;padding:2px 6px;border-radius:4px}\n"] }]
143
+ args: [{ selector: 'cwm-page-not-found', standalone: true, imports: [], template: "<div class=\"not-found-container\">\n <div class=\"not-found-card\">\n <div class=\"emoji-icon\">\uD83D\uDD0E</div>\n <div class=\"error-code\">404</div>\n <h1 class=\"error-title\">Page Not Found</h1>\n <p class=\"error-description\">\n The page you're looking for doesn't exist or has been moved. Let's get you back on track!\n </p>\n\n <div class=\"button-group\">\n <a routerLink=\"/\" class=\"btn btn-primary\">\uD83C\uDFE0 Go to Home</a>\n <a routerLink=\"/dashboard\" class=\"btn btn-secondary\">\uD83D\uDCCA Dashboard</a>\n <a href=\"javascript:history.back()\" class=\"btn btn-outline\">\u2190 Go Back</a>\n <a href=\"mailto:support@cheapweedmenu.com?subject=Broken%20link%20-%20404&body=I%20found%20a%20broken%20link\" class=\"btn btn-ghost\">\uD83D\uDCE7 Report Issue</a>\n </div>\n\n <div class=\"redirect-info\">\n <small class=\"redirect-timer\">You will be redirected to home in <span class=\"timer-badge\">{{ redirectCountdown }}</span> seconds</small>\n <div class=\"breadcrumb\">\n Requested URL: <code>{{ requestedUrl }}</code>\n </div>\n </div>\n </div>\n</div>\n", styles: [".menu{color:#fff}.menu-bg{background-color:#333}.hybrid{color:#375b2b}.hybrid-bg{background-color:#375b2b}.shake{color:#4e544c}.shake-bg{background-color:#4e544c}.indica{color:#1c2e16}.indica-bg{background-color:#1c2e16}.sativa{color:#d7d0c4}.sativa-bg{background-color:#d7d0c4}@keyframes float{0%,to{transform:translateY(0)}50%{transform:translateY(-20px)}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.not-found-container{min-height:70vh;display:flex;align-items:center;justify-content:center;padding:24px;background:linear-gradient(135deg,#1c2e16,#375b2b)}.not-found-card{max-width:720px;width:100%;text-align:center;border-radius:12px;box-shadow:0 8px 30px #0000004d;padding:36px;background:linear-gradient(180deg,#d7d0c4,#e8e2d8);animation:fadeInUp .6s ease-out}.emoji-icon{font-size:80px;line-height:1;margin-bottom:16px;animation:float 3s ease-in-out infinite;display:inline-block}.error-code{margin:0;font-size:48px;font-weight:700;color:#1c2e16;letter-spacing:-1px}.error-title{margin:8px 0;font-size:28px;font-weight:600;color:#1c2e16}.error-description{color:#4e544c;margin:12px 0 28px;line-height:1.6;font-size:16px}.button-group{display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin:32px 0}.btn{padding:12px 24px;border-radius:8px;text-decoration:none;font-weight:600;font-size:14px;transition:all .5s ease;cursor:pointer;border:none;display:inline-block}.btn-primary{background-color:#1c2e16;color:#d7d0c4;transition-property:all;transition-duration:.5s}.btn-primary:hover{background-color:#160816;color:#d7d0c4;transform:translateY(-2px);box-shadow:0 4px 12px #1c2e1666}.btn-secondary{background-color:#375b2b;color:#d7d0c4;transition-property:all;transition-duration:.5s}.btn-secondary:hover{background-color:#1c2e16;color:#d7d0c4;transform:translateY(-2px)}.btn-outline{background-color:#d7d0c4;color:#1c2e16;transition-property:all;transition-duration:.5s}.btn-outline:hover{background-color:#1c2e16;color:#d7d0c4;transform:translateY(-2px)}.btn-ghost{background:transparent;border:2px solid #1c2e16;color:#1c2e16;transition-property:all;transition-duration:.5s}.btn-ghost:hover{border-color:#375b2b;background-color:#375b2b;color:#d7d0c4;transform:translateY(-2px)}.redirect-info{margin-top:24px;padding-top:24px;border-top:2px solid #1c2e16}.redirect-timer{font-size:14px;color:#4e544c;margin-top:12px}.timer-badge{display:inline-block;background-color:#1c2e16;color:#d7d0c4;padding:4px 10px;border-radius:4px;font-weight:600;margin-left:4px}.breadcrumb{margin-top:20px;font-size:12px;color:#4e544c}.breadcrumb code{background-color:#1c2e16;color:#d7d0c4;padding:2px 6px;border-radius:4px}\n"] }]
143
144
  }], ctorParameters: () => [{ type: i1.Router }] });
144
145
 
145
146
  /*
@@ -346,29 +347,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImpo
346
347
  type: Injectable
347
348
  }], ctorParameters: () => [] });
348
349
 
349
- function msalGuardConfigFactory(authConfig, injector) {
350
- const logService = injector ? injector.get(ClassLoggerService, null) : null;
351
- if (logService) {
352
- //logService.className = 'AppModule';
353
- logService.methodName = 'msalGuardConfigFactory';
354
- logService.info(`msalGuardConfigFactory: using clientId=${authConfig.clientId}`);
355
- }
356
- else {
357
- console.info('msalGuardConfigFactory: auth clientId=', authConfig.clientId);
358
- }
359
- const scopes = authConfig && authConfig.scopes ? (Array.isArray(authConfig.scopes) ? authConfig.scopes : authConfig.scopes.split(',').map(s => s.trim())) : ['user.read'];
360
- const msalGuardConfiguration = {
361
- interactionType: InteractionType.Redirect,
362
- authRequest: {
363
- scopes: scopes,
364
- prompt: authConfig.prompt
365
- },
366
- };
367
- if (logService)
368
- logService.methodName = "";
369
- return msalGuardConfiguration;
370
- }
371
-
372
350
  function msalInstanceFactory(authConfig, injector) {
373
351
  const logService = injector ? injector.get(ClassLoggerService, null) : null;
374
352
  if (logService) {
@@ -467,6 +445,29 @@ function msalInstanceFactory(authConfig, injector) {
467
445
  return publicClientApplication;
468
446
  }
469
447
 
448
+ function msalGuardConfigFactory(authConfig, injector) {
449
+ const logService = injector ? injector.get(ClassLoggerService, null) : null;
450
+ if (logService) {
451
+ //logService.className = 'AppModule';
452
+ logService.methodName = 'msalGuardConfigFactory';
453
+ logService.info(`msalGuardConfigFactory: using clientId=${authConfig.clientId}`);
454
+ }
455
+ else {
456
+ console.info('msalGuardConfigFactory: auth clientId=', authConfig.clientId);
457
+ }
458
+ const scopes = authConfig && authConfig.scopes ? (Array.isArray(authConfig.scopes) ? authConfig.scopes : authConfig.scopes.split(',').map(s => s.trim())) : ['user.read'];
459
+ const msalGuardConfiguration = {
460
+ interactionType: InteractionType.Redirect,
461
+ authRequest: {
462
+ scopes: scopes,
463
+ prompt: authConfig.prompt
464
+ },
465
+ };
466
+ if (logService)
467
+ logService.methodName = "";
468
+ return msalGuardConfiguration;
469
+ }
470
+
470
471
  function msalInterceptorConfigFactory(authConfig, injector) {
471
472
  const logService = injector ? injector.get(ClassLoggerService, null) : null;
472
473
  if (logService) {
@@ -495,12 +496,23 @@ function msalInterceptorConfigFactory(authConfig, injector) {
495
496
 
496
497
  /* eslint-disable no-useless-catch */
497
498
  /* eslint-disable @typescript-eslint/no-explicit-any */
499
+ /**
500
+ * @module async-utils
501
+ * @description Utility functions for working with asynchronous code and managing concurrent task execution.
502
+ * Provides utilities for delays, promise management, and synchronization primitives for controlling
503
+ * the execution of async tasks to ensure only one task executes at a time for a given lock name.
504
+ */
498
505
  /**
499
506
  * A wrapper around setTimeout which returns a promise. Useful for waiting for an amount of
500
507
  * time from an async function. e.g. await waitFor(1000);
501
508
  *
502
509
  * @param milliseconds The amount of time to wait.
503
510
  * @returns A promise that resolves once the given number of milliseconds has ellapsed.
511
+ *
512
+ * @example
513
+ * // Wait for 2 seconds before continuing
514
+ * await waitFor(2000);
515
+ * console.log('2 seconds have passed');
504
516
  */
505
517
  function waitFor(milliseconds) {
506
518
  return new Promise((resolve) => {
@@ -519,9 +531,26 @@ const locksByName = {};
519
531
  * lock name for a group of tasks you can ensure the only one task will ever be in progress
520
532
  * at a given time.
521
533
  *
522
- * @param lockName The name of the lock to be obtained.
523
- * @param task The task to execute.
524
- * @returns The value returned by the task.
534
+ * Uses a queue-based locking mechanism where tasks are executed in FIFO order. Each task must
535
+ * complete (or throw an error) before the next task in the queue begins execution.
536
+ *
537
+ * @param lockName The name of the lock to be obtained. Tasks with the same lock name will be serialized.
538
+ * @param task The task to execute. Should return a Promise.
539
+ * @returns The value returned by the task. Rejects if the task throws an error.
540
+ *
541
+ * @example
542
+ * // Execute multiple API calls sequentially to prevent race conditions
543
+ * const result1 = await doWithLock('api-calls', async () => {
544
+ * return await fetchUser(userId);
545
+ * });
546
+ *
547
+ * @example
548
+ * // Multiple locks can run in parallel, but tasks with the same lock name are serialized
549
+ * await Promise.all([
550
+ * doWithLock('lock-a', () => slowTask1()),
551
+ * doWithLock('lock-a', () => slowTask2()), // Waits for slowTask1 to complete
552
+ * doWithLock('lock-b', () => otherTask()) // Runs in parallel with the lock-a tasks
553
+ * ]);
525
554
  */
526
555
  async function doWithLock(lockName, task) {
527
556
  // Ensure array present for the given lock name.
@@ -562,27 +591,20 @@ async function doWithLock(lockName, task) {
562
591
  }
563
592
  }
564
593
 
565
- class DbKeys {
566
- static CURRENT_USER = 'current_user';
567
- static USER_PERMISSIONS = 'user_permissions';
568
- static USER_DATA = 'user_data';
569
- static SYNC_KEYS = 'sync_keys';
570
- static AUTH_PROVIDER = 'auth_provider';
571
- static REMEMBER_ME = 'remember_me';
572
- static LANGUAGE = 'language';
573
- static HOME_URL = 'home_url';
574
- static THEME_ID = 'themeId';
575
- static SHOW_DASHBOARD_STATISTICS = 'show_dashboard_statistics';
576
- static SHOW_DASHBOARD_NOTIFICATIONS = 'show_dashboard_notifications';
577
- static SHOW_DASHBOARD_TODO = 'show_dashboard_todo';
578
- static SHOW_DASHBOARD_BANNER = 'show_dashboard_banner';
579
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: DbKeys, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
580
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: DbKeys });
581
- }
582
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: DbKeys, decorators: [{
583
- type: Injectable
584
- }] });
585
-
594
+ /**
595
+ * Decodes a JWT (JSON Web Token) and returns the decoded claims as an object.
596
+ *
597
+ * JWT tokens consist of three Base64-encoded parts separated by dots: header.payload.signature.
598
+ * This function decodes all parts and merges them into a single object, returning the combined result.
599
+ *
600
+ * @param token The JWT token string to decode. Can be a complete token or any of its parts.
601
+ * @returns An object containing the decoded claims from the token parts, or undefined if the token is empty or invalid.
602
+ *
603
+ * @example
604
+ * const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
605
+ * const decoded = decodeToken(token);
606
+ * // Returns: { alg: 'HS256', typ: 'JWT', sub: '1234567890', name: 'John Doe', iat: 1516239022 }
607
+ */
586
608
  function decodeToken(token) {
587
609
  if (!token) {
588
610
  return;
@@ -604,6 +626,27 @@ function decodeToken(token) {
604
626
  return acc;
605
627
  }, Object.create(null));
606
628
  }
629
+ /**
630
+ * Validates whether a JWT token is still valid based on its expiration time.
631
+ *
632
+ * This function checks the 'exp' (expiration) claim of a JWT token. The expiration time is
633
+ * expected to be a Unix timestamp (seconds since epoch). If the current time is less than the
634
+ * expiration time, the token is considered valid.
635
+ *
636
+ * @param input Either a JWT token string (from which the expiration will be extracted) or
637
+ * a Unix timestamp (expiration time in seconds). If invalid or undefined, returns false.
638
+ * @returns true if the token/timestamp has not yet expired, false otherwise.
639
+ *
640
+ * @example
641
+ * // Check if a token string is valid
642
+ * const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjk5OTk5OTk5OTl9.xxx';
643
+ * const isValid = isTokenValid(token); // true if not expired
644
+ *
645
+ * @example
646
+ * // Check if an expiration timestamp is valid
647
+ * const futureTimestamp = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now
648
+ * const isValid = isTokenValid(futureTimestamp); // true
649
+ */
607
650
  function isTokenValid(input) {
608
651
  if (!input) {
609
652
  return false;
@@ -612,15 +655,68 @@ function isTokenValid(input) {
612
655
  return exp ? Math.floor(Date.now() / 1000) < exp : false;
613
656
  }
614
657
 
615
- class TimeSpanOverflowError extends Error {
616
- }
617
-
618
658
  const MILLIS_PER_SECOND = 1000;
619
659
  const MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60; // 60,000
620
660
  const MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60; // 3,600,000
621
661
  const MILLIS_PER_DAY = MILLIS_PER_HOUR * 24; // 86,400,000
662
+ /**
663
+ * Represents an error that occurs when a TimeSpan operation would result in a value
664
+ * that exceeds the safe bounds of a number in JavaScript.
665
+ *
666
+ * This error is thrown when:
667
+ * - Creating a TimeSpan from a value that, when multiplied by a time scale (e.g., days, hours),
668
+ * would exceed {@link TimeSpan.maxValue} or fall below {@link TimeSpan.minValue}
669
+ * - Adding or subtracting TimeSpan instances would result in overflow
670
+ * - Converting time components that would exceed safe integer bounds
671
+ *
672
+ * The safe bounds are defined by JavaScript's {@link Number.MAX_SAFE_INTEGER} and
673
+ * {@link Number.MIN_SAFE_INTEGER}.
674
+ *
675
+ * @example
676
+ * try {
677
+ * // This will throw TimeSpanOverflowError
678
+ * const hugeSpan = TimeSpan.fromDays(Number.MAX_SAFE_INTEGER);
679
+ * } catch (error) {
680
+ * if (error instanceof TimeSpanOverflowError) {
681
+ * console.log('TimeSpan duration exceeded safe bounds');
682
+ * }
683
+ * }
684
+ */
685
+ class TimeSpanOverflowError extends Error {
686
+ }
687
+ /**
688
+ * Represents a time interval with support for days, hours, minutes, seconds, and milliseconds.
689
+ *
690
+ * The TimeSpan class provides a way to represent and work with time durations, similar to
691
+ * .NET's TimeSpan class. It stores time internally as milliseconds and provides various
692
+ * static factory methods to create instances from different time units.
693
+ *
694
+ * @example
695
+ * // Create a TimeSpan of 5 days
696
+ * const fiveDays = TimeSpan.fromDays(5);
697
+ *
698
+ * @example
699
+ * // Create a TimeSpan from specific time components
700
+ * const timespan = TimeSpan.fromTime(2, 30, 45); // 2 hours, 30 minutes, 45 seconds
701
+ *
702
+ * @example
703
+ * // Create a TimeSpan from a string representation
704
+ * const parsed = TimeSpan.fromString("5:30:45"); // 5 hours, 30 minutes, 45 seconds
705
+ */
622
706
  class TimeSpan {
707
+ /** @private The number of milliseconds represented by this TimeSpan */
623
708
  _millis;
709
+ /**
710
+ * @private
711
+ * Creates a TimeSpan from a value and time scale.
712
+ * Converts the value using the provided scale factor and rounds the result.
713
+ *
714
+ * @param value - The numeric value to convert
715
+ * @param scale - The scale factor (e.g., MILLIS_PER_DAY) to multiply by
716
+ * @returns A new TimeSpan instance
717
+ * @throws {Error} If value is NaN
718
+ * @throws {TimeSpanOverflowError} If the resulting milliseconds exceed safe integer bounds
719
+ */
624
720
  static interval(value, scale) {
625
721
  if (Number.isNaN(value)) {
626
722
  throw new Error("value can't be NaN");
@@ -632,6 +728,13 @@ class TimeSpan {
632
728
  }
633
729
  return new TimeSpan(millis);
634
730
  }
731
+ /**
732
+ * @private
733
+ * Rounds a number towards zero (floor for positive, ceil for negative).
734
+ *
735
+ * @param n - The number to round
736
+ * @returns The rounded number
737
+ */
635
738
  static round(n) {
636
739
  if (n < 0) {
637
740
  return Math.ceil(n);
@@ -641,6 +744,17 @@ class TimeSpan {
641
744
  }
642
745
  return 0;
643
746
  }
747
+ /**
748
+ * @private
749
+ * Converts time components (hours, minutes, seconds) to milliseconds.
750
+ * Validates that the total does not exceed TimeSpan bounds.
751
+ *
752
+ * @param hour - The number of hours (0-23 for typical usage)
753
+ * @param minute - The number of minutes (0-59)
754
+ * @param second - The number of seconds (0-59)
755
+ * @returns The total milliseconds
756
+ * @throws {TimeSpanOverflowError} If the total seconds exceed safe bounds
757
+ */
644
758
  static timeToMilliseconds(hour, minute, second) {
645
759
  const totalSeconds = (hour * 3600) + (minute * 60) + second;
646
760
  if (totalSeconds > TimeSpan.maxValue.totalSeconds || totalSeconds < TimeSpan.minValue.totalSeconds) {
@@ -648,27 +762,99 @@ class TimeSpan {
648
762
  }
649
763
  return totalSeconds * MILLIS_PER_SECOND;
650
764
  }
765
+ /**
766
+ * Gets a TimeSpan instance representing zero duration.
767
+ *
768
+ * @returns A TimeSpan with 0 milliseconds
769
+ */
651
770
  static get zero() {
652
771
  return new TimeSpan(0);
653
772
  }
773
+ /**
774
+ * Gets the maximum possible TimeSpan value.
775
+ * Uses JavaScript's MAX_SAFE_INTEGER as the underlying millisecond limit.
776
+ *
777
+ * @returns A TimeSpan with the maximum safe integer milliseconds
778
+ */
654
779
  static get maxValue() {
655
780
  return new TimeSpan(Number.MAX_SAFE_INTEGER);
656
781
  }
782
+ /**
783
+ * Gets the minimum possible TimeSpan value.
784
+ * Uses JavaScript's MIN_SAFE_INTEGER as the underlying millisecond limit.
785
+ *
786
+ * @returns A TimeSpan with the minimum safe integer milliseconds
787
+ */
657
788
  static get minValue() {
658
789
  return new TimeSpan(Number.MIN_SAFE_INTEGER);
659
790
  }
791
+ /**
792
+ * Creates a TimeSpan from a number of days.
793
+ *
794
+ * @param value - The number of days
795
+ * @returns A new TimeSpan instance representing the specified duration
796
+ * @throws {Error} If value is NaN
797
+ * @throws {TimeSpanOverflowError} If the result exceeds safe bounds
798
+ *
799
+ * @example
800
+ * const span = TimeSpan.fromDays(7); // 7 days
801
+ */
660
802
  static fromDays(value) {
661
803
  return TimeSpan.interval(value, MILLIS_PER_DAY);
662
804
  }
805
+ /**
806
+ * Creates a TimeSpan from a number of hours.
807
+ *
808
+ * @param value - The number of hours
809
+ * @returns A new TimeSpan instance representing the specified duration
810
+ * @throws {Error} If value is NaN
811
+ * @throws {TimeSpanOverflowError} If the result exceeds safe bounds
812
+ *
813
+ * @example
814
+ * const span = TimeSpan.fromHours(24); // 24 hours = 1 day
815
+ */
663
816
  static fromHours(value) {
664
817
  return TimeSpan.interval(value, MILLIS_PER_HOUR);
665
818
  }
819
+ /**
820
+ * Creates a TimeSpan from a number of milliseconds.
821
+ *
822
+ * @param value - The number of milliseconds
823
+ * @returns A new TimeSpan instance representing the specified duration
824
+ * @throws {Error} If value is NaN
825
+ * @throws {TimeSpanOverflowError} If the result exceeds safe bounds
826
+ *
827
+ * @example
828
+ * const span = TimeSpan.fromMilliseconds(5000); // 5 seconds
829
+ */
666
830
  static fromMilliseconds(value) {
667
831
  return TimeSpan.interval(value, 1);
668
832
  }
833
+ /**
834
+ * Creates a TimeSpan from a number of minutes.
835
+ *
836
+ * @param value - The number of minutes
837
+ * @returns A new TimeSpan instance representing the specified duration
838
+ * @throws {Error} If value is NaN
839
+ * @throws {TimeSpanOverflowError} If the result exceeds safe bounds
840
+ *
841
+ * @example
842
+ * const span = TimeSpan.fromMinutes(60); // 60 minutes = 1 hour
843
+ */
669
844
  static fromMinutes(value) {
670
845
  return TimeSpan.interval(value, MILLIS_PER_MINUTE);
671
846
  }
847
+ /**
848
+ * Creates a TimeSpan from a number of seconds.
849
+ *
850
+ * @param value - The number of seconds
851
+ * @returns A new TimeSpan instance representing the specified duration
852
+ * @throws {Error} If value is NaN
853
+ * @throws {TimeSpanOverflowError} If the result exceeds safe bounds
854
+ *
855
+ * @example
856
+ * const span = TimeSpan.fromSeconds(300); // 300 seconds = 5 minutes
857
+ */
672
858
  static fromSeconds(value) {
673
859
  return TimeSpan.interval(value, MILLIS_PER_SECOND);
674
860
  }
@@ -680,10 +866,32 @@ class TimeSpan {
680
866
  return this.fromTimeStartingFromHours(daysOrHours, hoursOrMinutes, minutesOrSeconds);
681
867
  }
682
868
  }
869
+ /**
870
+ * @private
871
+ * Creates a TimeSpan from hours, minutes, and seconds components.
872
+ *
873
+ * @param hours - The number of hours
874
+ * @param minutes - The number of minutes
875
+ * @param seconds - The number of seconds
876
+ * @returns A new TimeSpan instance
877
+ * @throws {TimeSpanOverflowError} If the total exceeds safe bounds
878
+ */
683
879
  static fromTimeStartingFromHours(hours, minutes, seconds) {
684
880
  const millis = TimeSpan.timeToMilliseconds(hours, minutes, seconds);
685
881
  return new TimeSpan(millis);
686
882
  }
883
+ /**
884
+ * @private
885
+ * Creates a TimeSpan from all time components (days through milliseconds).
886
+ *
887
+ * @param days - The number of days
888
+ * @param hours - The number of hours
889
+ * @param minutes - The number of minutes
890
+ * @param seconds - The number of seconds
891
+ * @param milliseconds - The number of milliseconds
892
+ * @returns A new TimeSpan instance
893
+ * @throws {TimeSpanOverflowError} If the total exceeds safe bounds
894
+ */
687
895
  static fromTimeStartingFromDays(days, hours, minutes, seconds, milliseconds) {
688
896
  const totalMilliSeconds = (days * MILLIS_PER_DAY) +
689
897
  (hours * MILLIS_PER_HOUR) +
@@ -695,47 +903,192 @@ class TimeSpan {
695
903
  }
696
904
  return new TimeSpan(totalMilliSeconds);
697
905
  }
906
+ /**
907
+ * Creates a new TimeSpan instance with the specified number of milliseconds.
908
+ *
909
+ * @param millis - The total number of milliseconds
910
+ *
911
+ * @example
912
+ * const span = new TimeSpan(5000); // 5 seconds
913
+ */
698
914
  constructor(millis) {
699
915
  this._millis = millis;
700
916
  }
917
+ /**
918
+ * Gets the day component of this TimeSpan (0-31+).
919
+ * This returns only the day portion, not the total days.
920
+ *
921
+ * @returns The number of whole days
922
+ * @example
923
+ * const span = TimeSpan.fromTime(1, 5, 30, 0); // 1 day, 5 hours, 30 minutes
924
+ * console.log(span.days); // 1
925
+ */
701
926
  get days() {
702
927
  return TimeSpan.round(this._millis / MILLIS_PER_DAY);
703
928
  }
929
+ /**
930
+ * Gets the hours component of this TimeSpan (0-23).
931
+ * This returns only the hours portion within a day, not the total hours.
932
+ *
933
+ * @returns The number of hours (0-23)
934
+ * @example
935
+ * const span = TimeSpan.fromTime(25, 30, 0); // 25 hours, 30 minutes
936
+ * console.log(span.hours); // 1 (25 % 24)
937
+ */
704
938
  get hours() {
705
939
  return TimeSpan.round((this._millis / MILLIS_PER_HOUR) % 24);
706
940
  }
941
+ /**
942
+ * Gets the minutes component of this TimeSpan (0-59).
943
+ * This returns only the minutes portion within an hour, not the total minutes.
944
+ *
945
+ * @returns The number of minutes (0-59)
946
+ * @example
947
+ * const span = TimeSpan.fromTime(0, 90, 0); // 90 minutes
948
+ * console.log(span.minutes); // 30 (90 % 60)
949
+ */
707
950
  get minutes() {
708
951
  return TimeSpan.round((this._millis / MILLIS_PER_MINUTE) % 60);
709
952
  }
953
+ /**
954
+ * Gets the seconds component of this TimeSpan (0-59).
955
+ * This returns only the seconds portion within a minute, not the total seconds.
956
+ *
957
+ * @returns The number of seconds (0-59)
958
+ * @example
959
+ * const span = TimeSpan.fromTime(0, 0, 90); // 90 seconds
960
+ * console.log(span.seconds); // 30 (90 % 60)
961
+ */
710
962
  get seconds() {
711
963
  return TimeSpan.round((this._millis / MILLIS_PER_SECOND) % 60);
712
964
  }
965
+ /**
966
+ * Gets the milliseconds component of this TimeSpan (0-999).
967
+ * This returns only the milliseconds portion within a second, not the total milliseconds.
968
+ *
969
+ * @returns The number of milliseconds (0-999)
970
+ * @example
971
+ * const span = TimeSpan.fromTime(0, 0, 0, 1, 500); // 1 second, 500 milliseconds
972
+ * console.log(span.milliseconds); // 500
973
+ */
713
974
  get milliseconds() {
714
975
  return TimeSpan.round(this._millis % 1000);
715
976
  }
977
+ /**
978
+ * Gets the total number of days in this TimeSpan.
979
+ * Unlike the {@link days} property, this returns the complete duration in days as a decimal.
980
+ *
981
+ * @returns The total duration in days
982
+ * @example
983
+ * const span = TimeSpan.fromHours(30); // 30 hours
984
+ * console.log(span.totalDays); // 1.25
985
+ */
716
986
  get totalDays() {
717
987
  return this._millis / MILLIS_PER_DAY;
718
988
  }
989
+ /**
990
+ * Gets the total number of hours in this TimeSpan.
991
+ * Unlike the {@link hours} property, this returns the complete duration in hours as a decimal.
992
+ *
993
+ * @returns The total duration in hours
994
+ * @example
995
+ * const span = TimeSpan.fromMinutes(90); // 90 minutes
996
+ * console.log(span.totalHours); // 1.5
997
+ */
719
998
  get totalHours() {
720
999
  return this._millis / MILLIS_PER_HOUR;
721
1000
  }
1001
+ /**
1002
+ * Gets the total number of minutes in this TimeSpan.
1003
+ * Unlike the {@link minutes} property, this returns the complete duration in minutes as a decimal.
1004
+ *
1005
+ * @returns The total duration in minutes
1006
+ * @example
1007
+ * const span = TimeSpan.fromSeconds(150); // 150 seconds
1008
+ * console.log(span.totalMinutes); // 2.5
1009
+ */
722
1010
  get totalMinutes() {
723
1011
  return this._millis / MILLIS_PER_MINUTE;
724
1012
  }
1013
+ /**
1014
+ * Gets the total number of seconds in this TimeSpan.
1015
+ * Unlike the {@link seconds} property, this returns the complete duration in seconds as a decimal.
1016
+ *
1017
+ * @returns The total duration in seconds
1018
+ * @example
1019
+ * const span = TimeSpan.fromMilliseconds(5500); // 5500 milliseconds
1020
+ * console.log(span.totalSeconds); // 5.5
1021
+ */
725
1022
  get totalSeconds() {
726
1023
  return this._millis / MILLIS_PER_SECOND;
727
1024
  }
1025
+ /**
1026
+ * Gets the total number of milliseconds in this TimeSpan.
1027
+ *
1028
+ * @returns The total duration in milliseconds
1029
+ * @example
1030
+ * const span = TimeSpan.fromSeconds(2); // 2 seconds
1031
+ * console.log(span.totalMilliseconds); // 2000
1032
+ */
728
1033
  get totalMilliseconds() {
729
1034
  return this._millis;
730
1035
  }
1036
+ /**
1037
+ * Returns a new TimeSpan that represents the sum of this instance and the specified TimeSpan.
1038
+ *
1039
+ * @param ts - The TimeSpan to add
1040
+ * @returns A new TimeSpan representing the sum
1041
+ * @throws {TimeSpanOverflowError} If the result exceeds safe integer bounds
1042
+ *
1043
+ * @example
1044
+ * const span1 = TimeSpan.fromHours(2);
1045
+ * const span2 = TimeSpan.fromHours(3);
1046
+ * const span3 = span1.add(span2);
1047
+ * console.log(span3.totalHours); // 5
1048
+ */
731
1049
  add(ts) {
732
1050
  const result = this._millis + ts.totalMilliseconds;
733
1051
  return new TimeSpan(result);
734
1052
  }
1053
+ /**
1054
+ * Returns a new TimeSpan that represents the difference between this instance and the specified TimeSpan.
1055
+ *
1056
+ * @param ts - The TimeSpan to subtract
1057
+ * @returns A new TimeSpan representing the difference
1058
+ * @throws {TimeSpanOverflowError} If the result exceeds safe integer bounds
1059
+ *
1060
+ * @example
1061
+ * const span1 = TimeSpan.fromHours(5);
1062
+ * const span2 = TimeSpan.fromHours(2);
1063
+ * const span3 = span1.subtract(span2);
1064
+ * console.log(span3.totalHours); // 3
1065
+ */
735
1066
  subtract(ts) {
736
1067
  const result = this._millis - ts.totalMilliseconds;
737
1068
  return new TimeSpan(result);
738
1069
  }
1070
+ /**
1071
+ * Creates a TimeSpan from a string representation.
1072
+ *
1073
+ * Supports two string formats:
1074
+ * 1. "HH:mm:ss" - Hours, minutes, seconds (e.g., "5:30:45")
1075
+ * 2. "d.HH:mm:ss.fff" - Days, hours, minutes, seconds, milliseconds (e.g., "5.02:30:45.500")
1076
+ *
1077
+ * @param value - The string representation of a TimeSpan
1078
+ * @returns A new TimeSpan instance parsed from the string
1079
+ * @throws {Error} If the string format is invalid or values cannot be parsed as integers
1080
+ *
1081
+ * @example
1082
+ * // Parse hours:minutes:seconds
1083
+ * const span1 = TimeSpan.fromString("5:30:45");
1084
+ * console.log(span1.totalSeconds); // 19845
1085
+ *
1086
+ * @example
1087
+ * // Parse days.hours:minutes:seconds.milliseconds
1088
+ * const span2 = TimeSpan.fromString("5.02:30:45.500");
1089
+ * console.log(span2.days); // 5
1090
+ * console.log(span2.milliseconds); // 500
1091
+ */
739
1092
  static fromString(value) {
740
1093
  const splitPoint = value.split('.');
741
1094
  if (splitPoint.length == 3) {
@@ -751,6 +1104,26 @@ class TimeSpan {
751
1104
 
752
1105
  /* eslint-disable @typescript-eslint/no-explicit-any */
753
1106
  /* eslint-disable no-useless-escape */
1107
+ /**
1108
+ * A comprehensive utility service providing static helper methods for common operations across the application.
1109
+ *
1110
+ * This service includes utilities for:
1111
+ * - **Cookie Management**: Getting, setting, removing, and checking cookies with proper encoding
1112
+ * - **Security**: Scrubbing sensitive data from logs to prevent accidental exposure of PII and credentials
1113
+ * - **HTTP Response Handling**: Parsing and extracting meaningful messages from HTTP responses and errors
1114
+ * - **Date/Time Formatting**: Converting dates to various human-readable formats and calculating durations
1115
+ * - **String Manipulation**: Case conversion, text expansion, trimming, and parsing operations
1116
+ * - **Data Processing**: Array operations, deep cloning, searching, and JSON parsing with fallback handling
1117
+ * - **Validation**: Type checking, URL validation, and network connectivity checks
1118
+ *
1119
+ * @example
1120
+ * // Using static utility methods
1121
+ * const scrubbedLog = Utilities.scrubForLog(sensitiveData);
1122
+ * const formattedDate = Utilities.printFriendlyDate(new Date());
1123
+ * const errorMessage = Utilities.getHttpResponseMessage(httpError);
1124
+ *
1125
+ * @injectable
1126
+ */
754
1127
  class Utilities {
755
1128
  static captionAndMessageSeparator = ':';
756
1129
  static noNetworkMessageCaption = 'No Network';
@@ -759,10 +1132,36 @@ class Utilities {
759
1132
  static accessDeniedMessageDetail = '';
760
1133
  static notFoundMessageCaption = 'Not Found';
761
1134
  static notFoundMessageDetail = 'The target resource cannot be found';
1135
+ /**
1136
+ * Cookie management utilities providing cross-browser compatible methods for cookie operations.
1137
+ * All cookie keys and values are properly URL-encoded/decoded to handle special characters safely.
1138
+ *
1139
+ * Methods:
1140
+ * - getItem: Retrieves a cookie value by name, returns null if not found
1141
+ * - setItem: Sets a cookie with optional expiration, path, domain, and secure flags
1142
+ * - removeItem: Removes a cookie by setting its expiration to the past
1143
+ * - hasItem: Checks if a cookie exists
1144
+ * - keys: Returns an array of all cookie names
1145
+ */
762
1146
  static cookies = {
1147
+ /**
1148
+ * Retrieves the value of a cookie by its name with proper URL decoding.
1149
+ * @param sKey The cookie name to retrieve
1150
+ * @returns The decoded cookie value or null if not found
1151
+ */
763
1152
  getItem: (sKey) => {
764
1153
  return decodeURIComponent(document.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1')) || null;
765
1154
  },
1155
+ /**
1156
+ * Sets a cookie with optional expiration, domain, path, and secure flag.
1157
+ * @param sKey The cookie name
1158
+ * @param sValue The cookie value
1159
+ * @param vEnd The expiration (Number for max-age in seconds, String for expires date, Date object, or null for session cookie)
1160
+ * @param sPath The path scope for the cookie (defaults to all paths if empty)
1161
+ * @param sDomain The domain scope for the cookie (defaults to current domain if empty)
1162
+ * @param bSecure Whether to set the Secure flag (cookie only sent over HTTPS)
1163
+ * @returns true if the cookie was set successfully, false otherwise
1164
+ */
766
1165
  setItem: (sKey, sValue, vEnd, sPath, sDomain, bSecure) => {
767
1166
  if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) {
768
1167
  return false;
@@ -784,6 +1183,13 @@ class Utilities {
784
1183
  document.cookie = encodeURIComponent(sKey) + '=' + encodeURIComponent(sValue) + sExpires + (sDomain ? '; domain=' + sDomain : '') + (sPath ? '; path=' + sPath : '') + (bSecure ? '; secure' : '');
785
1184
  return true;
786
1185
  },
1186
+ /**
1187
+ * Removes a cookie by setting its expiration to a past date.
1188
+ * @param sKey The cookie name to remove
1189
+ * @param sPath The path scope (must match the path used when setting the cookie)
1190
+ * @param sDomain The domain scope (must match the domain used when setting the cookie)
1191
+ * @returns true if the removal was attempted, false if key was invalid
1192
+ */
787
1193
  removeItem: (sKey, sPath, sDomain) => {
788
1194
  if (!sKey) {
789
1195
  return false;
@@ -791,9 +1197,18 @@ class Utilities {
791
1197
  document.cookie = encodeURIComponent(sKey) + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT' + (sDomain ? '; domain=' + sDomain : '') + (sPath ? '; path=' + sPath : '');
792
1198
  return true;
793
1199
  },
1200
+ /**
1201
+ * Checks if a cookie with the specified name exists.
1202
+ * @param sKey The cookie name to check
1203
+ * @returns true if the cookie exists, false otherwise
1204
+ */
794
1205
  hasItem: (sKey) => {
795
1206
  return (new RegExp('(?:^|;\\s*)' + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=')).test(document.cookie);
796
1207
  },
1208
+ /**
1209
+ * Retrieves all cookie names as an array.
1210
+ * @returns An array of all cookie names with proper URL decoding applied
1211
+ */
797
1212
  keys: () => {
798
1213
  // eslint-disable-next-line no-useless-backreference
799
1214
  const aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, '').split(/\s*(?:\=[^;]*)?;\s*/);
@@ -848,6 +1263,13 @@ class Utilities {
848
1263
  return '[UNABLE_TO_SCRUB]';
849
1264
  }
850
1265
  }
1266
+ /**
1267
+ * Extracts all messages from an HTTP response or error, formatting them as key-value pairs.
1268
+ * Automatically detects network errors, access denied, and not found responses with special formatting.
1269
+ *
1270
+ * @param data The HTTP response or error object
1271
+ * @returns An array of formatted message strings in the format "Key: Value"
1272
+ */
851
1273
  static getHttpResponseMessages(data) {
852
1274
  const responses = [];
853
1275
  if (data instanceof HttpResponseBase) {
@@ -897,6 +1319,13 @@ class Utilities {
897
1319
  }
898
1320
  return responses;
899
1321
  }
1322
+ /**
1323
+ * Extracts a single consolidated message from an HTTP response or error.
1324
+ * Searches in priority order: network message, not found message, error_description, error, then general response messages.
1325
+ *
1326
+ * @param data The HTTP response or error object
1327
+ * @returns A single formatted message string
1328
+ */
900
1329
  static getHttpResponseMessage(data) {
901
1330
  const httpMessage = Utilities.findHttpResponseMessage(Utilities.noNetworkMessageCaption, data) ||
902
1331
  Utilities.findHttpResponseMessage(Utilities.notFoundMessageCaption, data) ||
@@ -905,6 +1334,16 @@ class Utilities {
905
1334
  Utilities.getHttpResponseMessages(data).join();
906
1335
  return httpMessage;
907
1336
  }
1337
+ /**
1338
+ * Searches for a specific message within HTTP response messages.
1339
+ * Can search in caption only or in full message text, and can optionally include the caption in the result.
1340
+ *
1341
+ * @param messageToFind The message or caption text to search for (case-insensitive)
1342
+ * @param data The HTTP response or error object
1343
+ * @param searchInCaptionOnly If true, only searches in the caption portion (before separator), defaults to true
1344
+ * @param includeCaptionInResult If true, includes the caption in the returned result, defaults to false
1345
+ * @returns The matching message or null if not found
1346
+ */
908
1347
  static findHttpResponseMessage(messageToFind, data, searchInCaptionOnly = true, includeCaptionInResult = false) {
909
1348
  const searchString = messageToFind.toLowerCase();
910
1349
  const httpMessages = this.getHttpResponseMessages(data);
@@ -929,6 +1368,13 @@ class Utilities {
929
1368
  }
930
1369
  return null;
931
1370
  }
1371
+ /**
1372
+ * Extracts the response body from an HTTP response or error.
1373
+ * For successful responses, returns the body property. For error responses, prioritizes error over message and statusText.
1374
+ *
1375
+ * @param response The HTTP response or error response
1376
+ * @returns The response body, error, or null if none available
1377
+ */
932
1378
  static getResponseBody(response) {
933
1379
  if (response instanceof HttpResponse) {
934
1380
  return response.body;
@@ -937,24 +1383,45 @@ class Utilities {
937
1383
  return response.error || response.message || response.statusText;
938
1384
  }
939
1385
  }
1386
+ /**
1387
+ * Checks if the response indicates a network error (status 0).
1388
+ * @param response The HTTP response to check
1389
+ * @returns true if status is 0 (no network connection), false otherwise
1390
+ */
940
1391
  static checkNoNetwork(response) {
941
1392
  if (response instanceof HttpResponseBase) {
942
1393
  return response.status == 0;
943
1394
  }
944
1395
  return false;
945
1396
  }
1397
+ /**
1398
+ * Checks if the response indicates an access denied error (status 403).
1399
+ * @param response The HTTP response to check
1400
+ * @returns true if status is 403 (Forbidden), false otherwise
1401
+ */
946
1402
  static checkAccessDenied(response) {
947
1403
  if (response instanceof HttpResponseBase) {
948
1404
  return response.status == 403;
949
1405
  }
950
1406
  return false;
951
1407
  }
1408
+ /**
1409
+ * Checks if the response indicates a not found error (status 404).
1410
+ * @param response The HTTP response to check
1411
+ * @returns true if status is 404 (Not Found), false otherwise
1412
+ */
952
1413
  static checkNotFound(response) {
953
1414
  if (response instanceof HttpResponseBase) {
954
1415
  return response.status == 404;
955
1416
  }
956
1417
  return false;
957
1418
  }
1419
+ /**
1420
+ * Validates if a URL points to localhost or 127.0.0.1.
1421
+ * @param url The URL to check
1422
+ * @param base Optional base URL for resolving relative URLs
1423
+ * @returns true if the URL hostname is localhost or 127.0.0.1, false otherwise
1424
+ */
958
1425
  static checkIsLocalHost(url, base) {
959
1426
  if (url) {
960
1427
  const location = new URL(url, base);
@@ -962,6 +1429,13 @@ class Utilities {
962
1429
  }
963
1430
  return false;
964
1431
  }
1432
+ /**
1433
+ * Parses a query parameter string into a key-value object.
1434
+ * Handles standard URL query string format (key=value&key=value).
1435
+ *
1436
+ * @param paramString The query parameter string (e.g., "id=123&name=test")
1437
+ * @returns An object with key-value pairs, or null if paramString is empty
1438
+ */
965
1439
  static getQueryParamsFromString(paramString) {
966
1440
  if (!paramString) {
967
1441
  return null;
@@ -973,6 +1447,14 @@ class Utilities {
973
1447
  }
974
1448
  return params;
975
1449
  }
1450
+ /**
1451
+ * Splits a string into two parts based on the first occurrence of a separator.
1452
+ * Both parts are trimmed of whitespace.
1453
+ *
1454
+ * @param text The text to split
1455
+ * @param separator The separator string to split on
1456
+ * @returns An object with firstPart and secondPart (secondPart is null if separator not found)
1457
+ */
976
1458
  static splitInTwo(text, separator) {
977
1459
  const separatorIndex = text.indexOf(separator);
978
1460
  if (separatorIndex == -1) {
@@ -982,6 +1464,13 @@ class Utilities {
982
1464
  const part2 = text.substr(separatorIndex + 1).trim();
983
1465
  return { firstPart: part1, secondPart: part2 };
984
1466
  }
1467
+ /**
1468
+ * Safely stringifies an object, filtering out complex types and handling circular references.
1469
+ * If direct JSON.stringify fails, creates a sanitized object containing only primitive properties.
1470
+ *
1471
+ * @param object The object to stringify
1472
+ * @returns A JSON string representation of the object, or a sanitized version if stringification fails
1473
+ */
985
1474
  static safeStringify(object) {
986
1475
  let result;
987
1476
  try {
@@ -1007,6 +1496,13 @@ class Utilities {
1007
1496
  result = '[***Sanitized Object***]: ' + JSON.stringify(simpleObject);
1008
1497
  return result;
1009
1498
  }
1499
+ /**
1500
+ * Safely parses a JSON string with fallback handling.
1501
+ * Returns the parsed object if valid JSON, or returns undefined/value if parsing fails.
1502
+ *
1503
+ * @param value The JSON string to parse
1504
+ * @returns The parsed object, 'undefined' if the string is 'undefined', or the original value if parsing fails
1505
+ */
1010
1506
  static JsonTryParse(value) {
1011
1507
  try {
1012
1508
  return JSON.parse(value);
@@ -1018,6 +1514,11 @@ class Utilities {
1018
1514
  return value;
1019
1515
  }
1020
1516
  }
1517
+ /**
1518
+ * Checks if an object has no properties.
1519
+ * @param obj The object to check
1520
+ * @returns true if the object is empty (no own properties), false otherwise
1521
+ */
1021
1522
  static TestIsObjectEmpty(obj) {
1022
1523
  for (const prop in obj) {
1023
1524
  if (Object.prototype.hasOwnProperty.call(obj, prop)) {
@@ -1026,13 +1527,28 @@ class Utilities {
1026
1527
  }
1027
1528
  return true;
1028
1529
  }
1530
+ /**
1531
+ * Checks if a value is undefined.
1532
+ * @param value The value to check
1533
+ * @returns true if the value is undefined, false otherwise
1534
+ */
1029
1535
  static TestIsUndefined(value) {
1030
1536
  return typeof value === 'undefined';
1031
1537
  // return value === undefined;
1032
1538
  }
1539
+ /**
1540
+ * Checks if a value is a string.
1541
+ * @param value The value to check
1542
+ * @returns true if the value is a string primitive or String object, false otherwise
1543
+ */
1033
1544
  static TestIsString(value) {
1034
1545
  return typeof value === 'string' || value instanceof String;
1035
1546
  }
1547
+ /**
1548
+ * Capitalizes the first letter of a string while leaving the rest unchanged.
1549
+ * @param text The text to capitalize
1550
+ * @returns The text with the first character uppercased, or the original text if empty
1551
+ */
1036
1552
  static capitalizeFirstLetter(text) {
1037
1553
  if (text) {
1038
1554
  return text.charAt(0).toUpperCase() + text.slice(1);
@@ -1041,7 +1557,12 @@ class Utilities {
1041
1557
  return text;
1042
1558
  }
1043
1559
  }
1044
- static toTitleCase(text) {
1560
+ /**
1561
+ * Converts a string to title case, capitalizing the first letter of each word.
1562
+ * @param text The text to convert to title case
1563
+ * @returns The text in title case format
1564
+ */
1565
+ static toTitleCase(text) {
1045
1566
  return text.replace(/\w\S*/g, (subString) => {
1046
1567
  return subString.charAt(0).toUpperCase() + subString.substr(1).toLowerCase();
1047
1568
  });
@@ -1058,12 +1579,28 @@ class Utilities {
1058
1579
  return items.toLowerCase();
1059
1580
  }
1060
1581
  }
1582
+ /**
1583
+ * Generates a unique random ID string.
1584
+ * @returns A string representation of a random number between 1,000,000 and 9,000,000
1585
+ */
1061
1586
  static uniqueId() {
1062
1587
  return this.randomNumber(1000000, 9000000).toString();
1063
1588
  }
1589
+ /**
1590
+ * Generates a random integer within a specified range (inclusive).
1591
+ * @param min The minimum value (inclusive)
1592
+ * @param max The maximum value (inclusive)
1593
+ * @returns A random integer between min and max
1594
+ */
1064
1595
  static randomNumber(min, max) {
1065
1596
  return Math.floor(Math.random() * (max - min + 1) + min);
1066
1597
  }
1598
+ /**
1599
+ * Gets the base URL of the current application.
1600
+ * Handles cases where window.location.origin is not available by constructing it from components.
1601
+ *
1602
+ * @returns The base URL without trailing slash (e.g., "http://localhost:4200")
1603
+ */
1067
1604
  static baseUrl() {
1068
1605
  let base = '';
1069
1606
  if (window.location.origin) {
@@ -1074,6 +1611,13 @@ class Utilities {
1074
1611
  }
1075
1612
  return base.replace(/\/$/, '');
1076
1613
  }
1614
+ /**
1615
+ * Formats a date to display only the date portion in human-readable format.
1616
+ * Example output: "Monday, 1st January 2024"
1617
+ *
1618
+ * @param date The date to format
1619
+ * @returns A formatted date string with day of week, day of month with ordinal suffix, month name, and year
1620
+ */
1077
1621
  static printDateOnly(date) {
1078
1622
  date = new Date(date);
1079
1623
  const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
@@ -1098,6 +1642,13 @@ class Utilities {
1098
1642
  const dateString = dayNames[dayOfWeek] + ', ' + dayOfMonth + sup + ' ' + monthNames[month] + ' ' + year;
1099
1643
  return dateString;
1100
1644
  }
1645
+ /**
1646
+ * Formats a date to display only the time portion in 12-hour format with AM/PM.
1647
+ * Example output: "2:30 PM"
1648
+ *
1649
+ * @param date The date to format
1650
+ * @returns A formatted time string in HH:MM AM/PM format
1651
+ */
1101
1652
  static printTimeOnly(date) {
1102
1653
  date = new Date(date);
1103
1654
  let period = '';
@@ -1116,9 +1667,25 @@ class Utilities {
1116
1667
  const timeString = hour + ':' + minute + ' ' + period;
1117
1668
  return timeString;
1118
1669
  }
1670
+ /**
1671
+ * Combines date and time formatting into a single readable string.
1672
+ * Example output: "Monday, 1st January 2024 at 2:30 PM"
1673
+ *
1674
+ * @param date The date to format
1675
+ * @param separator The text to insert between date and time (defaults to 'at')
1676
+ * @returns A combined date and time string
1677
+ */
1119
1678
  static printDate(date, separator = 'at') {
1120
1679
  return `${Utilities.printDateOnly(date)} ${separator} ${Utilities.printTimeOnly(date)}`;
1121
1680
  }
1681
+ /**
1682
+ * Formats a date using friendly relative terminology for recent dates.
1683
+ * Returns "Today", "Yesterday", or full date for older dates.
1684
+ *
1685
+ * @param date The date to format
1686
+ * @param separator The text to insert between "Today"/"Yesterday" and time (defaults to '-')
1687
+ * @returns A friendly date string (e.g., "Today - 2:30 PM" or full date for older dates)
1688
+ */
1122
1689
  static printFriendlyDate(date, separator = '-') {
1123
1690
  const today = new Date();
1124
1691
  today.setHours(0, 0, 0, 0);
@@ -1135,6 +1702,15 @@ class Utilities {
1135
1702
  return Utilities.printDate(date, separator);
1136
1703
  }
1137
1704
  }
1705
+ /**
1706
+ * Formats a date to a short format with time.
1707
+ * Example output: "03/01/2024 - 2:30 PM"
1708
+ *
1709
+ * @param date The date to format
1710
+ * @param separator The separator between month, day, and year (defaults to '/')
1711
+ * @param dateTimeSeparator The separator between date and time (defaults to '-')
1712
+ * @returns A formatted short date string with time
1713
+ */
1138
1714
  static printShortDate(date, separator = '/', dateTimeSeparator = '-') {
1139
1715
  let day = date.getDate().toString();
1140
1716
  let month = (date.getMonth() + 1).toString();
@@ -1147,6 +1723,14 @@ class Utilities {
1147
1723
  }
1148
1724
  return `${month}${separator}${day}${separator}${year} ${dateTimeSeparator} ${Utilities.printTimeOnly(date)}`;
1149
1725
  }
1726
+ /**
1727
+ * Parses various date formats into a Date object.
1728
+ * Supports Date objects, ISO strings, and timestamps (milliseconds).
1729
+ * Automatically appends 'Z' to date strings without timezone information.
1730
+ *
1731
+ * @param date The date in various formats (Date, string, or number/timestamp)
1732
+ * @returns A Date object, or an empty string if input is null/undefined
1733
+ */
1150
1734
  static parseDate(date) {
1151
1735
  if (date) {
1152
1736
  if (date instanceof Date) {
@@ -1164,6 +1748,15 @@ class Utilities {
1164
1748
  }
1165
1749
  return "";
1166
1750
  }
1751
+ /**
1752
+ * Calculates and formats the duration between two dates.
1753
+ * Breaks down the duration into days, hours, minutes, and seconds.
1754
+ * Example output: "2 days, 3 hours, 15 minutes and 30 seconds"
1755
+ *
1756
+ * @param start The start date
1757
+ * @param end The end date
1758
+ * @returns A human-readable duration string
1759
+ */
1167
1760
  static printDuration(start, end) {
1168
1761
  start = new Date(start);
1169
1762
  end = new Date(end);
@@ -1198,6 +1791,14 @@ class Utilities {
1198
1791
  }
1199
1792
  return printedDays;
1200
1793
  }
1794
+ /**
1795
+ * Calculates the age in years between two dates.
1796
+ * Properly accounts for whether the birthday has occurred in the current year.
1797
+ *
1798
+ * @param birthDate The birth date (can be string, number timestamp, or Date object)
1799
+ * @param otherDate The reference date to calculate age from (can be string, number timestamp, or Date object)
1800
+ * @returns The age in years as an integer
1801
+ */
1201
1802
  static getAge(birthDate, otherDate) {
1202
1803
  birthDate = new Date(birthDate);
1203
1804
  otherDate = new Date(otherDate);
@@ -1208,6 +1809,13 @@ class Utilities {
1208
1809
  }
1209
1810
  return years;
1210
1811
  }
1812
+ /**
1813
+ * Performs a deep clone of an array, recursively copying nested arrays and objects.
1814
+ * Primitive types are returned unchanged, and handles circular references safely.
1815
+ *
1816
+ * @param items The array to clone
1817
+ * @returns A deep copy of the array with all nested structures cloned
1818
+ */
1211
1819
  static deepCloneArray(items) {
1212
1820
  return items.map(item => {
1213
1821
  if (Array.isArray(item)) {
@@ -1224,6 +1832,15 @@ class Utilities {
1224
1832
  return item; // Return primitive types unchanged
1225
1833
  });
1226
1834
  }
1835
+ /**
1836
+ * Searches for a term within an array of values using case-sensitive or case-insensitive matching.
1837
+ * Joins all values into a single string and searches for the term within it.
1838
+ *
1839
+ * @param searchTerm The term to search for
1840
+ * @param caseSensitive Whether the search should be case-sensitive (defaults to false)
1841
+ * @param values Variable number of values to search within
1842
+ * @returns true if the search term is found, false otherwise
1843
+ */
1227
1844
  static searchArray(searchTerm, caseSensitive, ...values) {
1228
1845
  if (!searchTerm) {
1229
1846
  return true;
@@ -1236,6 +1853,14 @@ class Utilities {
1236
1853
  }
1237
1854
  return data.indexOf(filter) != -1;
1238
1855
  }
1856
+ /**
1857
+ * Moves an item within an array from one index to another.
1858
+ * Handles negative indices (counts from the end) and automatically extends the array if needed.
1859
+ *
1860
+ * @param array The array to modify
1861
+ * @param oldIndex The current index of the item to move
1862
+ * @param newIndex The target index to move the item to
1863
+ */
1239
1864
  static moveArrayItem(array, oldIndex, newIndex) {
1240
1865
  if (oldIndex < 0) {
1241
1866
  return;
@@ -1251,6 +1876,14 @@ class Utilities {
1251
1876
  }
1252
1877
  array.splice(newIndex, 0, array.splice(oldIndex, 1)[0]);
1253
1878
  }
1879
+ /**
1880
+ * Expands camelCase text into space-separated words.
1881
+ * Handles both camelCase and PascalCase patterns as well as non-alphanumeric characters.
1882
+ * Example: "myPropertyName" becomes "my Property Name"
1883
+ *
1884
+ * @param text The camelCase text to expand
1885
+ * @returns The expanded text with spaces between words
1886
+ */
1254
1887
  static expandCamelCase(text) {
1255
1888
  if (!text) {
1256
1889
  return text;
@@ -1259,10 +1892,24 @@ class Utilities {
1259
1892
  .replace(/([A-Z][A-Z]+)/g, ' $1')
1260
1893
  .replace(/([^A-Za-z ]+)/g, ' $1');
1261
1894
  }
1895
+ /**
1896
+ * Tests whether a URL is absolute (starts with protocol or //).
1897
+ * Matches URLs like "http://example.com" or "//example.com"
1898
+ *
1899
+ * @param url The URL to test
1900
+ * @returns true if the URL is absolute, false if it's relative
1901
+ */
1262
1902
  static testIsAbsoluteUrl(url) {
1263
1903
  const r = new RegExp('^(?:[a-z]+:)?//', 'i');
1264
1904
  return r.test(url);
1265
1905
  }
1906
+ /**
1907
+ * Converts a relative URL to an absolute URL by prepending //.
1908
+ * If the URL is already absolute, it's returned unchanged.
1909
+ *
1910
+ * @param url The URL to convert
1911
+ * @returns The absolute URL (e.g., "//example.com" for "example.com")
1912
+ */
1266
1913
  static convertToAbsoluteUrl(url) {
1267
1914
  return Utilities.testIsAbsoluteUrl(url) ? url : '//' + url;
1268
1915
  }
@@ -1533,203 +2180,182 @@ const MENU_BOARD_CONFIG_SCHEMA = {
1533
2180
  required: ["amountToScroll", "autoScrollTimeout", "dataAgeThreshold"]
1534
2181
  };
1535
2182
 
1536
- const DEFAULT_SLIDE = {
1537
- "image": "../../../assets/images/tuesday.png",
1538
- "title": "Doobie Tuesday",
1539
- "description": "10% off all pre-rolls & packs<",
1540
- "highlighted": false,
1541
- "textColor": "black",
1542
- "backgroundColor": "white"
1543
- };
1544
- const SLIDE_SCHEMA = {
2183
+ class CoordinatesStyle {
2184
+ top;
2185
+ left;
2186
+ ngOnStyle(ngStyle) {
2187
+ if (this.top)
2188
+ ngStyle['top'] = typeof this.top === 'number' ? `${this.top}px` : this.top;
2189
+ if (this.left)
2190
+ ngStyle['left'] = typeof this.left === 'number' ? `${this.left}px` : this.left;
2191
+ return ngStyle;
2192
+ }
2193
+ }
2194
+ const DEFAULT_COORDINATES = (() => {
2195
+ const coordinates = new CoordinatesStyle();
2196
+ coordinates.top = 0;
2197
+ coordinates.left = 0;
2198
+ return coordinates;
2199
+ })();
2200
+ const COORDINATES_STYLE_SCHEMA = {
1545
2201
  type: "object",
1546
- title: "Slide",
2202
+ title: "CoordinatesStyle",
1547
2203
  properties: {
1548
- image: {
1549
- type: "string",
1550
- title: "Image URL", // Custom display name
1551
- description: "The URL of background image for the slide",
1552
- default: "../../../assets/images/tuesday.png"
1553
- },
1554
- title: {
1555
- type: "string",
1556
- title: "Title",
1557
- description: "The title text displayed on the slide",
1558
- default: "Doobie Tuesday"
1559
- },
1560
- description: {
1561
- type: "string",
1562
- title: "Description",
1563
- description: "The description text displayed on the slide",
1564
- default: "10% off all pre-rolls & packs<"
1565
- },
1566
- highlighted: {
1567
- type: "boolean",
1568
- title: "Highlighted",
1569
- description: "Determines if the slide is highlighted",
1570
- default: false
1571
- },
1572
- textColor: {
1573
- type: "string",
1574
- title: "Text Color",
1575
- description: "The color of the text on the slide",
1576
- enum: ['red', 'green', 'blue', 'yellow', 'black', 'white'],
1577
- default: "black"
2204
+ top: {
2205
+ type: "number",
2206
+ title: "Top coordinates", // Custom display name
2207
+ description: "Number that describes the top coordinates in pixels",
2208
+ default: 1
1578
2209
  },
1579
- backgroundColor: {
1580
- type: "string",
1581
- title: "Background Color",
1582
- description: "The background color of the slide",
1583
- enum: ['red', 'green', 'blue', 'yellow', 'black', 'white'],
1584
- default: "white"
2210
+ left: {
2211
+ type: "number",
2212
+ title: "Left coordinates",
2213
+ description: "Number that describes the left coordinates in pixels",
2214
+ default: 1
1585
2215
  }
1586
2216
  },
1587
- required: [
1588
- "image",
1589
- "title",
1590
- "description",
1591
- "highlighted",
1592
- "textColor",
1593
- "backgroundColor"
1594
- ]
2217
+ required: ["top", "left"]
1595
2218
  };
1596
2219
 
1597
- const DEFAULT_SUBSCRIPTION_CONFIG = {
1598
- amount: 1,
1599
- days: 30,
1600
- totalOccurrences: 12,
1601
- trialOccurrences: 0
2220
+ var Justifications;
2221
+ (function (Justifications) {
2222
+ Justifications["Left"] = "left";
2223
+ Justifications["Right"] = "right";
2224
+ Justifications["Center"] = "center";
2225
+ Justifications["Fully"] = "fully";
2226
+ })(Justifications || (Justifications = {}));
2227
+ const DEFAULT_JUSTIFICATION = Justifications.Left;
2228
+ const JUSTIFICATIONS_SCHEMA = {
2229
+ type: "string",
2230
+ title: "Justification",
2231
+ description: "Justification of the image within its container",
2232
+ enum: Object.values(Justifications),
2233
+ default: DEFAULT_JUSTIFICATION
1602
2234
  };
1603
- const SUBSCRIPTION_CONFIG_SCHEMA = {
2235
+
2236
+ class SizeStyle {
2237
+ ngOnStyle(ngStyle) {
2238
+ if (this.width) {
2239
+ if (ngStyle) {
2240
+ ngStyle['width'] = typeof this.width === 'number' ? `${this.width}px` : this.width;
2241
+ }
2242
+ }
2243
+ if (this.height) {
2244
+ if (ngStyle) {
2245
+ ngStyle['height'] = typeof this.height === 'number' ? `${this.height}px` : this.height;
2246
+ }
2247
+ }
2248
+ return ngStyle;
2249
+ }
2250
+ width;
2251
+ height;
2252
+ }
2253
+ const DEFAULT_SIZE_STYLE = (() => {
2254
+ const sizeStyle = new SizeStyle();
2255
+ sizeStyle.height = 100;
2256
+ sizeStyle.width = 100;
2257
+ return sizeStyle;
2258
+ })();
2259
+ const SIZE_STYLE_SCHEMA = {
1604
2260
  type: "object",
1605
- title: "Subscription Configuration",
2261
+ title: "SizeStyle",
1606
2262
  properties: {
1607
- amount: {
1608
- type: "number",
1609
- title: "Cost per occurrence", // Custom display name
1610
- description: "The amount to charge for each subscription occurrence",
1611
- default: 1
1612
- },
1613
- days: {
1614
- type: "number",
1615
- title: "Days between occurrences",
1616
- description: "The number of days between each subscription occurrence",
1617
- default: 30
1618
- },
1619
- totalOccurrences: {
1620
- type: "number",
1621
- title: "Total occurrences",
1622
- description: "The total number of occurrences for the subscription",
1623
- default: 12
1624
- },
1625
- trialOccurrences: {
1626
- type: "number",
1627
- title: "Trial occurrences",
1628
- description: "The number of trial occurrences (free)",
1629
- default: 0
2263
+ width: {
2264
+ type: ["number", "string"],
2265
+ title: "Width",
2266
+ description: "Width of the element, can be a number (in pixels) or a string (e.g., '100px', '50%')"
2267
+ },
2268
+ height: {
2269
+ type: ["number", "string"],
2270
+ title: "Height",
2271
+ description: "Height of the element, can be a number (in pixels) or a string (e.g., '100px', '50%')"
1630
2272
  }
1631
2273
  },
1632
- required: ["amount", "days", "totalOccurrences", "trialOccurrences"]
2274
+ required: ["width", "height"]
1633
2275
  };
1634
2276
 
1635
- const DEFAULT_COMPANY_NAME = 'Test Weed Dispensary';
1636
- const DEFAULT_CONFIGURATION = {
1637
- companyName: DEFAULT_COMPANY_NAME,
1638
- authConfig: DEFAULT_AUTH_CONFIG,
1639
- apiConfig: DEFAULT_API_CONFIG,
1640
- subscriptionConfig: DEFAULT_SUBSCRIPTION_CONFIG,
1641
- menuBoardConfig: DEFAULT_MENU_BOARD_CONFIG,
1642
- footerCarouselConfig: DEFAULT_CAROUSEL_CONFIG,
1643
- footerCarouselSlideConfig: [
1644
- {
1645
- "image": "../../../assets/images/sunday.png",
1646
- "title": "CBD Sunday",
1647
- "description": "20% off all CBD products & house pre-rolls",
1648
- "highlighted": false,
1649
- "textColor": "white",
1650
- "backgroundColor": "black"
1651
- },
1652
- {
1653
- "image": "../../../assets/images/new-monday.jpg",
1654
- "title": "Edible Monday",
1655
- "description": "10% off edibles",
1656
- "highlighted": false,
1657
- "textColor": "white",
1658
- "backgroundColor": "black"
1659
- },
1660
- {
1661
- "image": "../../../assets/images/tuesday.png",
1662
- "title": "Doobie Tuesday",
1663
- "description": "10% off all pre-rolls & packs<",
1664
- "highlighted": false,
1665
- "textColor": "black",
1666
- "backgroundColor": "white"
1667
- },
1668
- {
1669
- "image": "../../../assets/images/wednesday.png",
1670
- "title": "Weed Crush Wednesday",
1671
- "description": "10% off all jar bud",
1672
- "highlighted": false,
1673
- "textColor": "black",
1674
- "backgroundColor": "white"
1675
- },
1676
- {
1677
- "image": "../../../assets/images/thursday.png",
1678
- "title": "Thirsty Thursday",
1679
- "description": "10% off Tinctures, RSO & cartridges",
1680
- "highlighted": false,
1681
- "textColor": "black",
1682
- "backgroundColor": "white"
1683
- },
1684
- {
1685
- "image": "../../../assets/images/friday.png",
1686
- "title": "Fire Friday",
1687
- "description": "10% off all jar bud",
1688
- "highlighted": false,
1689
- "textColor": "black",
1690
- "backgroundColor": "white"
1691
- },
1692
- {
1693
- "image": "../../../assets/images/saturday.png",
1694
- "title": "Shatterday Saturday",
1695
- "description": "10% off all concentrate",
1696
- "highlighted": false,
1697
- "textColor": "white",
1698
- "backgroundColor": "black"
2277
+ class ImageStyle {
2278
+ href;
2279
+ justification;
2280
+ size;
2281
+ coordinates;
2282
+ ngOnStyle(ngStyle) {
2283
+ if (this.href) {
2284
+ ngStyle['backgroundImage'] = `url(${this.href})`;
1699
2285
  }
1700
- ]
1701
- };
1702
- const CONFIGURATION_SCHEMA = {
1703
- type: "object",
1704
- title: "Customer Configuration",
2286
+ if (this.justification) {
2287
+ ngStyle['backgroundPosition'] = this.justification;
2288
+ }
2289
+ if (this.size) {
2290
+ this.size.ngOnStyle(ngStyle);
2291
+ }
2292
+ if (this.coordinates) {
2293
+ this.coordinates.ngOnStyle(ngStyle);
2294
+ }
2295
+ return ngStyle;
2296
+ }
2297
+ }
2298
+ const DEFAULT_IMAGE_STYLE = (() => {
2299
+ const image = new ImageStyle();
2300
+ image.href = "assets/images/default-category.png";
2301
+ image.justification = Justifications.Left;
2302
+ image.size = DEFAULT_SIZE_STYLE;
2303
+ image.coordinates = DEFAULT_COORDINATES;
2304
+ return image;
2305
+ })();
2306
+ const IMAGE_STYLE_SCHEMA = {
2307
+ type: "object",
2308
+ title: "ImageStyle",
1705
2309
  properties: {
1706
- companyName: {
2310
+ href: {
1707
2311
  type: "string",
1708
- title: "Company Name",
1709
- default: DEFAULT_COMPANY_NAME
2312
+ title: "Image URL",
2313
+ description: "URL of the image to be used as background"
1710
2314
  },
1711
- authConfig: AUTH_CONFIG_SCHEMA,
1712
- apiConfig: API_CONFIG_SCHEMA,
1713
- subscriptionConfig: SUBSCRIPTION_CONFIG_SCHEMA,
1714
- menuBoardConfig: MENU_BOARD_CONFIG_SCHEMA,
1715
- footerCarouselConfig: CAROUSEL_CONFIG_SCHEMA,
1716
- footerCarouselSlideConfig: {
1717
- type: "array",
1718
- title: "Footer Carousel Slides Collection",
1719
- description: "Configuration for each slide in the footer carousel",
1720
- items: SLIDE_SCHEMA,
1721
- default: DEFAULT_CONFIGURATION.footerCarouselSlideConfig
2315
+ justification: {
2316
+ enum: Object.values(Justifications),
2317
+ type: "string",
2318
+ title: "Justification",
2319
+ description: "Justification of the image within its container"
2320
+ },
2321
+ size: {
2322
+ type: "object",
2323
+ title: "SizeStyle",
2324
+ description: "Size of the image",
2325
+ properties: {
2326
+ width: {
2327
+ type: ["number", "string"],
2328
+ title: "Width",
2329
+ description: "Width of the image in pixels or as a CSS string (e.g., '100px', '50%')"
2330
+ },
2331
+ height: {
2332
+ type: ["number", "string"],
2333
+ title: "Height",
2334
+ description: "Height of the image in pixels or as a CSS string (e.g., '100px', '50%')"
2335
+ }
2336
+ },
2337
+ required: ["width", "height"]
2338
+ },
2339
+ coordinates: {
2340
+ type: "object",
2341
+ title: "CoordinatesStyle",
2342
+ description: "Position of the image within its container",
2343
+ properties: {
2344
+ top: {
2345
+ type: ["number", "string"],
2346
+ title: "Top coordinates",
2347
+ description: "Number that describes the top coordinates in pixels or as a CSS string (e.g., '10px', '5%')"
2348
+ },
2349
+ left: {
2350
+ type: ["number", "string"],
2351
+ title: "Left coordinates",
2352
+ description: "Number that describes the left coordinates in pixels or as a CSS string (e.g., '10px', '5%')"
2353
+ }
2354
+ },
2355
+ required: ["top", "left"]
1722
2356
  }
1723
2357
  },
1724
- required: [
1725
- "companyName",
1726
- "authConfig",
1727
- "apiConfig",
1728
- "subscriptionConfig",
1729
- "menuBoardConfig",
1730
- "footerCarouselConfig",
1731
- "footerCarouselSlideConfig"
1732
- ]
2358
+ required: ["href", "justification"]
1733
2359
  };
1734
2360
 
1735
2361
  var NamedColors;
@@ -2065,6 +2691,14 @@ function isNamedColor(colorName) {
2065
2691
  function getNamedColors() {
2066
2692
  return Object.keys(NAMED_COLOR_MAP);
2067
2693
  }
2694
+ const DEFAULT_NAMED_COLOR = NamedColors.Black;
2695
+ const NAMED_COLOR_SCHEMA = {
2696
+ type: "string",
2697
+ title: "Named Color",
2698
+ description: "A CSS named color (e.g., 'red', 'blue', 'lightgray')",
2699
+ enum: getNamedColors(),
2700
+ default: DEFAULT_NAMED_COLOR
2701
+ };
2068
2702
 
2069
2703
  class RgbaColorStyle {
2070
2704
  ngOnStyle(ngStyle) {
@@ -2171,6 +2805,46 @@ class RgbaColorStyle {
2171
2805
  return null;
2172
2806
  }
2173
2807
  }
2808
+ const DEFAULT_RGBA_COLOR_STYLE = RgbaColorStyle.fromHexColor("#000000");
2809
+ const RGBA_COLOR_STYLE_SCHEMA = {
2810
+ type: "object",
2811
+ title: "RGBA Color Style",
2812
+ properties: {
2813
+ r: {
2814
+ type: "number",
2815
+ title: "Red",
2816
+ description: "Red component (0-255)",
2817
+ minimum: 0,
2818
+ maximum: 255,
2819
+ default: 0
2820
+ },
2821
+ g: {
2822
+ type: "number",
2823
+ title: "Green",
2824
+ description: "Green component (0-255)",
2825
+ minimum: 0,
2826
+ maximum: 255,
2827
+ default: 0
2828
+ },
2829
+ b: {
2830
+ type: "number",
2831
+ title: "Blue",
2832
+ description: "Blue component (0-255)",
2833
+ minimum: 0,
2834
+ maximum: 255,
2835
+ default: 0
2836
+ },
2837
+ a: {
2838
+ type: "number",
2839
+ title: "Alpha",
2840
+ description: "Alpha component (0-1)",
2841
+ minimum: 0,
2842
+ maximum: 1,
2843
+ default: 1
2844
+ }
2845
+ },
2846
+ required: ["r", "g", "b", "a"]
2847
+ };
2174
2848
 
2175
2849
  /**
2176
2850
  * A unified Color class that can represent colors as Named, Hex, or RGBA formats
@@ -2390,50 +3064,431 @@ class ColorStyle {
2390
3064
  cloned.a = rgba.a;
2391
3065
  return new ColorStyle(cloned);
2392
3066
  }
2393
- return new ColorStyle(this._value);
2394
- }
2395
- }
2396
- const DEFAULT_FORECOLOR = ColorStyle.fromNamedColor("black");
2397
- const DEFAULT_BACKCOLOR = ColorStyle.fromNamedColor("white");
2398
-
2399
- class CoordinatesStyle {
2400
- top;
2401
- left;
2402
- ngOnStyle(ngStyle) {
2403
- if (this.top)
2404
- ngStyle['top'] = typeof this.top === 'number' ? `${this.top}px` : this.top;
2405
- if (this.left)
2406
- ngStyle['left'] = typeof this.left === 'number' ? `${this.left}px` : this.left;
2407
- return ngStyle;
2408
- }
2409
- }
2410
- const DEFAULT_COORDINATES = new CoordinatesStyle();
2411
- DEFAULT_COORDINATES.top = 0;
2412
- DEFAULT_COORDINATES.left = 0;
2413
-
2414
- class FontStyle {
2415
- family;
2416
- size;
2417
- italic;
2418
- bold;
2419
- underline;
2420
- ngOnStyle(ngStyle) {
2421
- if (this.family)
2422
- ngStyle['fontFamily'] = this.family;
2423
- if (this.size)
2424
- ngStyle['fontSize'] = `${this.size}px`;
2425
- ngStyle['fontStyle'] = this.italic ? 'italic' : 'normal';
2426
- ngStyle['fontWeight'] = this.bold ? 'bold' : 'normal';
2427
- ngStyle['textDecoration'] = this.underline ? 'underline' : 'none';
2428
- return ngStyle;
2429
- }
2430
- }
2431
- const DEFAULT_FONT = new FontStyle();
2432
- DEFAULT_FONT.family = '"Newake-Regular", sans-serif';
2433
- DEFAULT_FONT.size = 16;
2434
- DEFAULT_FONT.italic = false;
2435
- DEFAULT_FONT.bold = true;
2436
- DEFAULT_FONT.underline = false;
3067
+ return new ColorStyle(this._value);
3068
+ }
3069
+ }
3070
+ const DEFAULT_FORECOLOR = ColorStyle.fromNamedColor("black");
3071
+ const DEFAULT_BACKCOLOR = ColorStyle.fromNamedColor("white");
3072
+
3073
+ class FontStyle {
3074
+ family;
3075
+ size;
3076
+ italic;
3077
+ bold;
3078
+ underline;
3079
+ ngOnStyle(ngStyle) {
3080
+ if (this.family)
3081
+ ngStyle['fontFamily'] = this.family;
3082
+ if (this.size)
3083
+ ngStyle['fontSize'] = `${this.size}px`;
3084
+ ngStyle['fontStyle'] = this.italic ? 'italic' : 'normal';
3085
+ ngStyle['fontWeight'] = this.bold ? 'bold' : 'normal';
3086
+ ngStyle['textDecoration'] = this.underline ? 'underline' : 'none';
3087
+ return ngStyle;
3088
+ }
3089
+ }
3090
+ const DEFAULT_FONT = (() => {
3091
+ const font = new FontStyle();
3092
+ font.family = '"Newake-Regular", sans-serif';
3093
+ font.size = 16;
3094
+ font.italic = false;
3095
+ font.bold = true;
3096
+ font.underline = false;
3097
+ return font;
3098
+ })();
3099
+ const FONT_STYLE_SCHEMA = {
3100
+ type: "object",
3101
+ title: "FontStyle",
3102
+ properties: {
3103
+ family: {
3104
+ type: "string",
3105
+ title: "Font family", // Custom display name
3106
+ description: "String that describes the font family, e.g., 'Arial', 'Times New Roman', 'Newake-Regular'",
3107
+ default: '"Newake-Regular", sans-serif'
3108
+ },
3109
+ size: {
3110
+ type: "number",
3111
+ title: "Font size",
3112
+ description: "Number that describes the font size in pixels",
3113
+ default: 16
3114
+ },
3115
+ italic: {
3116
+ type: "boolean",
3117
+ title: "Italic",
3118
+ description: "Boolean that indicates whether the font is italic or not",
3119
+ default: false
3120
+ },
3121
+ bold: {
3122
+ type: "boolean",
3123
+ title: "Bold",
3124
+ description: "Boolean that indicates whether the font is bold or not",
3125
+ default: false
3126
+ },
3127
+ underline: {
3128
+ type: "boolean",
3129
+ title: "Underline",
3130
+ description: "Boolean that indicates whether the font is underlined or not",
3131
+ default: false
3132
+ }
3133
+ },
3134
+ required: ["family", "size"]
3135
+ };
3136
+
3137
+ class TextStyle {
3138
+ font;
3139
+ textColor;
3140
+ backgroundColor;
3141
+ watermark;
3142
+ image;
3143
+ value;
3144
+ ngOnStyle(ngStyle) {
3145
+ return {
3146
+ ...(ngStyle),
3147
+ ...(this.font ? this.font.ngOnStyle(ngStyle) : {}),
3148
+ ...(this.textColor ? { color: this.textColor.toCssString() } : {}),
3149
+ ...(this.backgroundColor ? { backgroundColor: this.backgroundColor.toCssString() } : {}),
3150
+ ...(this.image ? this.image.ngOnStyle(ngStyle) : {}),
3151
+ };
3152
+ }
3153
+ }
3154
+ const DEFAULT_TEXT_STYLE = (() => {
3155
+ const textStyle = new TextStyle();
3156
+ textStyle.value = 'Default Category';
3157
+ textStyle.font = DEFAULT_FONT;
3158
+ textStyle.textColor = DEFAULT_BACKCOLOR;
3159
+ textStyle.backgroundColor = DEFAULT_FORECOLOR;
3160
+ textStyle.watermark = "Category Name";
3161
+ textStyle.image = DEFAULT_IMAGE_STYLE;
3162
+ return textStyle;
3163
+ })();
3164
+ const TEXT_STYLE_SCHEMA = {
3165
+ type: "object",
3166
+ title: "TextStyle",
3167
+ properties: {
3168
+ value: {
3169
+ type: "string",
3170
+ title: "Text Value",
3171
+ description: "The actual text content to be displayed"
3172
+ },
3173
+ font: {
3174
+ type: "object",
3175
+ title: "FontStyle",
3176
+ description: "Font styling for the text",
3177
+ properties: {
3178
+ family: {
3179
+ type: "string",
3180
+ title: "Font Family",
3181
+ description: "The font family to be used for the text (e.g., 'Arial', 'Helvetica')"
3182
+ },
3183
+ size: {
3184
+ type: ["number", "string"],
3185
+ title: "Font Size",
3186
+ description: "The size of the font, can be a number (in pixels) or a string (e.g., '16px', '1em')"
3187
+ },
3188
+ weight: {
3189
+ type: ["number", "string"],
3190
+ title: "Font Weight",
3191
+ description: "The weight of the font, can be a number (e.g., 400 for normal, 700 for bold) or a string (e.g., 'normal', 'bold')"
3192
+ },
3193
+ style: {
3194
+ type: "string",
3195
+ title: "Font Style",
3196
+ description: "The style of the font (e.g., 'normal', 'italic')"
3197
+ },
3198
+ decoration: {
3199
+ type: "string",
3200
+ title: "Text Decoration",
3201
+ description: "The decoration of the text (e.g., 'none', 'underline')"
3202
+ }
3203
+ }
3204
+ },
3205
+ textColor: {
3206
+ type: "object",
3207
+ title: "Text Color",
3208
+ description: "Color styling for the text, defined as an object with properties for color values"
3209
+ },
3210
+ backgroundColor: {
3211
+ type: "object",
3212
+ title: "Background Color",
3213
+ description: "Color styling for the background, defined as an object with propertiesfor color values"
3214
+ },
3215
+ watermark: {
3216
+ type: "string",
3217
+ title: "Watermark",
3218
+ description: "A string that represents the watermark text to be displayed behind the main text"
3219
+ },
3220
+ image: {
3221
+ type: "object",
3222
+ title: "ImageStyle",
3223
+ description: "Image styling for the text, defined as an object with properties for image URL, size, and position"
3224
+ }
3225
+ }
3226
+ };
3227
+
3228
+ class FooterSlide {
3229
+ image;
3230
+ title;
3231
+ description;
3232
+ highlighted;
3233
+ backgroundColor;
3234
+ }
3235
+ const DEFAULT_SLIDE = {
3236
+ "image": "../../../assets/images/tuesday.png",
3237
+ "title": "Doobie Tuesday",
3238
+ "description": "10% off all pre-rolls & packs<",
3239
+ "highlighted": false,
3240
+ "textColor": "black",
3241
+ "backgroundColor": "white"
3242
+ };
3243
+ const DEFAULT_FOOTER_SLIDE = (() => {
3244
+ const slide = new FooterSlide();
3245
+ slide.image = (() => {
3246
+ const imageStyle = new ImageStyle();
3247
+ imageStyle.href = "../../../assets/images/tuesday.png";
3248
+ return imageStyle;
3249
+ })();
3250
+ slide.title = (() => {
3251
+ const textStyle = new TextStyle();
3252
+ textStyle.value = "Doobie Tuesday";
3253
+ textStyle.textColor = ColorStyle.fromNamedColor(NamedColors.Black);
3254
+ if (textStyle.font)
3255
+ textStyle.font.size = 14;
3256
+ return textStyle;
3257
+ })();
3258
+ slide.description = (() => {
3259
+ const textStyle = new TextStyle();
3260
+ textStyle.value = "10% off all pre-rolls & packs";
3261
+ textStyle.textColor = ColorStyle.fromNamedColor(NamedColors.Black);
3262
+ if (textStyle.font)
3263
+ textStyle.font.size = 12;
3264
+ return textStyle;
3265
+ })();
3266
+ slide.highlighted = false;
3267
+ slide.backgroundColor = ColorStyle.fromNamedColor(NamedColors.White);
3268
+ return slide;
3269
+ })();
3270
+ const SLIDE_SCHEMA = {
3271
+ type: "object",
3272
+ title: "Slide",
3273
+ properties: {
3274
+ image: {
3275
+ type: "string",
3276
+ title: "Image URL", // Custom display name
3277
+ description: "The URL of background image for the slide",
3278
+ default: "../../../assets/images/tuesday.png"
3279
+ },
3280
+ title: {
3281
+ type: "string",
3282
+ title: "Title",
3283
+ description: "The title text displayed on the slide",
3284
+ default: "Doobie Tuesday"
3285
+ },
3286
+ description: {
3287
+ type: "string",
3288
+ title: "Description",
3289
+ description: "The description text displayed on the slide",
3290
+ default: "10% off all pre-rolls & packs<"
3291
+ },
3292
+ highlighted: {
3293
+ type: "boolean",
3294
+ title: "Highlighted",
3295
+ description: "Determines if the slide is highlighted",
3296
+ default: false
3297
+ },
3298
+ textColor: {
3299
+ type: "string",
3300
+ title: "Text Color",
3301
+ description: "The color of the text on the slide",
3302
+ enum: ['red', 'green', 'blue', 'yellow', 'black', 'white'],
3303
+ default: "black"
3304
+ },
3305
+ backgroundColor: {
3306
+ type: "string",
3307
+ title: "Background Color",
3308
+ description: "The background color of the slide",
3309
+ enum: ['red', 'green', 'blue', 'yellow', 'black', 'white'],
3310
+ default: "white"
3311
+ }
3312
+ },
3313
+ required: [
3314
+ "image",
3315
+ "title",
3316
+ "description",
3317
+ "highlighted",
3318
+ "textColor",
3319
+ "backgroundColor"
3320
+ ]
3321
+ };
3322
+ const FOOTER_SLIDE_SCHEMA = {
3323
+ type: "object",
3324
+ title: "FooterSlide",
3325
+ properties: {
3326
+ image: {
3327
+ type: "object",
3328
+ title: "ImageStyle",
3329
+ description: "The styling for the image displayed on the footer slide"
3330
+ },
3331
+ title: {
3332
+ type: "object",
3333
+ title: "TextStyle",
3334
+ description: "The styling for the title text displayed on the footer slide"
3335
+ },
3336
+ description: {
3337
+ type: "object",
3338
+ title: "TextStyle",
3339
+ description: "The styling for the description text displayed on the footer slide"
3340
+ },
3341
+ highlighted: {
3342
+ type: "boolean",
3343
+ title: "Highlighted",
3344
+ description: "Determines if the footer slide is highlighted"
3345
+ },
3346
+ backgroundColor: {
3347
+ type: "object",
3348
+ title: "ColorStyle",
3349
+ description: "The background color styling for the footer slide"
3350
+ }
3351
+ },
3352
+ required: ["image", "title"]
3353
+ };
3354
+
3355
+ const DEFAULT_SUBSCRIPTION_CONFIG = {
3356
+ amount: 1,
3357
+ days: 30,
3358
+ totalOccurrences: 12,
3359
+ trialOccurrences: 0
3360
+ };
3361
+ const SUBSCRIPTION_CONFIG_SCHEMA = {
3362
+ type: "object",
3363
+ title: "Subscription Configuration",
3364
+ properties: {
3365
+ amount: {
3366
+ type: "number",
3367
+ title: "Cost per occurrence", // Custom display name
3368
+ description: "The amount to charge for each subscription occurrence",
3369
+ default: 1
3370
+ },
3371
+ days: {
3372
+ type: "number",
3373
+ title: "Days between occurrences",
3374
+ description: "The number of days between each subscription occurrence",
3375
+ default: 30
3376
+ },
3377
+ totalOccurrences: {
3378
+ type: "number",
3379
+ title: "Total occurrences",
3380
+ description: "The total number of occurrences for the subscription",
3381
+ default: 12
3382
+ },
3383
+ trialOccurrences: {
3384
+ type: "number",
3385
+ title: "Trial occurrences",
3386
+ description: "The number of trial occurrences (free)",
3387
+ default: 0
3388
+ }
3389
+ },
3390
+ required: ["amount", "days", "totalOccurrences", "trialOccurrences"]
3391
+ };
3392
+
3393
+ const DEFAULT_COMPANY_NAME = 'Test Weed Dispensary';
3394
+ const DEFAULT_CONFIGURATION = {
3395
+ companyName: DEFAULT_COMPANY_NAME,
3396
+ authConfig: DEFAULT_AUTH_CONFIG,
3397
+ apiConfig: DEFAULT_API_CONFIG,
3398
+ subscriptionConfig: DEFAULT_SUBSCRIPTION_CONFIG,
3399
+ menuBoardConfig: DEFAULT_MENU_BOARD_CONFIG,
3400
+ footerCarouselConfig: DEFAULT_CAROUSEL_CONFIG,
3401
+ footerCarouselSlideConfig: [
3402
+ {
3403
+ "image": "../../../assets/images/sunday.png",
3404
+ "title": "CBD Sunday",
3405
+ "description": "20% off all CBD products & house pre-rolls",
3406
+ "highlighted": false,
3407
+ "textColor": "white",
3408
+ "backgroundColor": "black"
3409
+ },
3410
+ {
3411
+ "image": "../../../assets/images/new-monday.jpg",
3412
+ "title": "Edible Monday",
3413
+ "description": "10% off edibles",
3414
+ "highlighted": false,
3415
+ "textColor": "white",
3416
+ "backgroundColor": "black"
3417
+ },
3418
+ {
3419
+ "image": "../../../assets/images/tuesday.png",
3420
+ "title": "Doobie Tuesday",
3421
+ "description": "10% off all pre-rolls & packs<",
3422
+ "highlighted": false,
3423
+ "textColor": "black",
3424
+ "backgroundColor": "white"
3425
+ },
3426
+ {
3427
+ "image": "../../../assets/images/wednesday.png",
3428
+ "title": "Weed Crush Wednesday",
3429
+ "description": "10% off all jar bud",
3430
+ "highlighted": false,
3431
+ "textColor": "black",
3432
+ "backgroundColor": "white"
3433
+ },
3434
+ {
3435
+ "image": "../../../assets/images/thursday.png",
3436
+ "title": "Thirsty Thursday",
3437
+ "description": "10% off Tinctures, RSO & cartridges",
3438
+ "highlighted": false,
3439
+ "textColor": "black",
3440
+ "backgroundColor": "white"
3441
+ },
3442
+ {
3443
+ "image": "../../../assets/images/friday.png",
3444
+ "title": "Fire Friday",
3445
+ "description": "10% off all jar bud",
3446
+ "highlighted": false,
3447
+ "textColor": "black",
3448
+ "backgroundColor": "white"
3449
+ },
3450
+ {
3451
+ "image": "../../../assets/images/saturday.png",
3452
+ "title": "Shatterday Saturday",
3453
+ "description": "10% off all concentrate",
3454
+ "highlighted": false,
3455
+ "textColor": "white",
3456
+ "backgroundColor": "black"
3457
+ }
3458
+ ]
3459
+ };
3460
+ const CONFIGURATION_SCHEMA = {
3461
+ type: "object",
3462
+ title: "Customer Configuration",
3463
+ properties: {
3464
+ companyName: {
3465
+ type: "string",
3466
+ title: "Company Name",
3467
+ default: DEFAULT_COMPANY_NAME
3468
+ },
3469
+ authConfig: AUTH_CONFIG_SCHEMA,
3470
+ apiConfig: API_CONFIG_SCHEMA,
3471
+ subscriptionConfig: SUBSCRIPTION_CONFIG_SCHEMA,
3472
+ menuBoardConfig: MENU_BOARD_CONFIG_SCHEMA,
3473
+ footerCarouselConfig: CAROUSEL_CONFIG_SCHEMA,
3474
+ footerCarouselSlideConfig: {
3475
+ type: "array",
3476
+ title: "Footer Carousel Slides Collection",
3477
+ description: "Configuration for each slide in the footer carousel",
3478
+ items: SLIDE_SCHEMA,
3479
+ default: DEFAULT_CONFIGURATION.footerCarouselSlideConfig
3480
+ }
3481
+ },
3482
+ required: [
3483
+ "companyName",
3484
+ "authConfig",
3485
+ "apiConfig",
3486
+ "subscriptionConfig",
3487
+ "menuBoardConfig",
3488
+ "footerCarouselConfig",
3489
+ "footerCarouselSlideConfig"
3490
+ ]
3491
+ };
2437
3492
 
2438
3493
  /**
2439
3494
  * Converts a hex color to RGBA format
@@ -2674,87 +3729,26 @@ function rgbaToNamedColor(rgba) {
2674
3729
  const hexColor = rgbaObjectToHex(rgba);
2675
3730
  return hexToNamedColor(hexColor);
2676
3731
  }
3732
+ const DEFAULT_HEX_COLOR = "#000000";
3733
+ const HEX_COLOR_SCHEMA = {
3734
+ type: "string",
3735
+ title: "HexColor",
3736
+ description: "A hex color string in the format #RRGGBB or #RRGGBBAA",
3737
+ pattern: "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
3738
+ default: DEFAULT_HEX_COLOR
3739
+ };
2677
3740
 
2678
- var Justifications;
2679
- (function (Justifications) {
2680
- Justifications["Left"] = "left";
2681
- Justifications["Right"] = "right";
2682
- Justifications["Center"] = "center";
2683
- Justifications["Fully"] = "fully";
2684
- })(Justifications || (Justifications = {}));
2685
-
2686
- class SizeStyle {
2687
- ngOnStyle(ngStyle) {
2688
- if (this.width) {
2689
- if (ngStyle) {
2690
- ngStyle['width'] = typeof this.width === 'number' ? `${this.width}px` : this.width;
2691
- }
2692
- }
2693
- if (this.height) {
2694
- if (ngStyle) {
2695
- ngStyle['height'] = typeof this.height === 'number' ? `${this.height}px` : this.height;
2696
- }
2697
- }
2698
- return ngStyle;
2699
- }
2700
- width;
2701
- height;
2702
- }
2703
- const DEFAULT_SIZE = new SizeStyle();
2704
- DEFAULT_SIZE.width = 100;
2705
- DEFAULT_SIZE.height = 100;
2706
-
2707
- class ImageStyle {
2708
- href;
2709
- justification;
2710
- size;
2711
- coordinates;
2712
- ngOnStyle(ngStyle) {
2713
- if (this.href) {
2714
- ngStyle['backgroundImage'] = `url(${this.href})`;
2715
- }
2716
- if (this.justification) {
2717
- ngStyle['backgroundPosition'] = this.justification;
2718
- }
2719
- if (this.size) {
2720
- this.size.ngOnStyle(ngStyle);
2721
- }
2722
- if (this.coordinates) {
2723
- this.coordinates.ngOnStyle(ngStyle);
2724
- }
2725
- return ngStyle;
2726
- }
2727
- }
2728
- const DEFAULT_IMAGE = new ImageStyle();
2729
- DEFAULT_IMAGE.href = "assets/images/default-category.png";
2730
- DEFAULT_IMAGE.justification = Justifications.Left;
2731
- DEFAULT_IMAGE.size = DEFAULT_SIZE;
2732
- DEFAULT_IMAGE.coordinates = DEFAULT_COORDINATES;
2733
-
2734
- class TextStyle {
2735
- font;
2736
- textColor;
2737
- backgroundColor;
2738
- watermark;
2739
- image;
2740
- value;
2741
- ngOnStyle(ngStyle) {
2742
- return {
2743
- ...(ngStyle),
2744
- ...(this.font ? this.font.ngOnStyle(ngStyle) : {}),
2745
- ...(this.textColor ? { color: this.textColor.toCssString() } : {}),
2746
- ...(this.backgroundColor ? { backgroundColor: this.backgroundColor.toCssString() } : {}),
2747
- ...(this.image ? this.image.ngOnStyle(ngStyle) : {}),
2748
- };
3741
+ const DEFAULT_NG_STYLE = { width: 100 };
3742
+ const NG_STYLE_SCHEMA = {
3743
+ type: "object",
3744
+ title: "ngStyleAttribute",
3745
+ description: "Object representing CSS styles to be applied to an element",
3746
+ additionalProperties: {
3747
+ type: ["string", "number", "null"],
3748
+ title: "CSS Property Value",
3749
+ description: "Value of the CSS property, can be a string, number, or null"
2749
3750
  }
2750
- }
2751
- const DEFAULT_TEXT_STYLE = new TextStyle();
2752
- DEFAULT_TEXT_STYLE.value = 'Default Category';
2753
- DEFAULT_TEXT_STYLE.font = DEFAULT_FONT;
2754
- DEFAULT_TEXT_STYLE.textColor = DEFAULT_BACKCOLOR;
2755
- DEFAULT_TEXT_STYLE.backgroundColor = DEFAULT_FORECOLOR;
2756
- DEFAULT_TEXT_STYLE.watermark = "Category Name";
2757
- DEFAULT_TEXT_STYLE.image = DEFAULT_IMAGE;
3751
+ };
2758
3752
 
2759
3753
  const EXAMPLE_CREATE_SUBSCRIPTION_REQUEST = {
2760
3754
  "creditCard": {
@@ -28744,76 +29738,16 @@ class SafeHtmlPipe {
28744
29738
  return this.sanitizer.bypassSecurityTrustHtml(value);
28745
29739
  }
28746
29740
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: SafeHtmlPipe, deps: [{ token: i1$1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Pipe });
28747
- static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.19", ngImport: i0, type: SafeHtmlPipe, isStandalone: false, name: "safeHtml" });
29741
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.19", ngImport: i0, type: SafeHtmlPipe, isStandalone: true, name: "safeHtml" });
28748
29742
  }
28749
29743
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: SafeHtmlPipe, decorators: [{
28750
29744
  type: Pipe,
28751
29745
  args: [{
28752
29746
  name: 'safeHtml',
28753
- standalone: false
29747
+ standalone: true
28754
29748
  }]
28755
29749
  }], ctorParameters: () => [{ type: i1$1.DomSanitizer }] });
28756
29750
 
28757
- class CwmSharedModule {
28758
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: CwmSharedModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
28759
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.19", ngImport: i0, type: CwmSharedModule, declarations: [ExternalNavigationComponent,
28760
- NavigateToRouteComponent,
28761
- PageNotFoundComponent,
28762
- SafeHtmlPipe], imports: [CommonModule,
28763
- RouterModule,
28764
- MsalModule,
28765
- BrowserModule,
28766
- FormsModule], exports: [CommonModule,
28767
- RouterModule,
28768
- MsalModule,
28769
- ExternalNavigationComponent,
28770
- NavigateToRouteComponent,
28771
- PageNotFoundComponent,
28772
- SafeHtmlPipe] });
28773
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: CwmSharedModule, imports: [CommonModule,
28774
- RouterModule,
28775
- MsalModule,
28776
- BrowserModule,
28777
- FormsModule, CommonModule,
28778
- RouterModule,
28779
- MsalModule] });
28780
- }
28781
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: CwmSharedModule, decorators: [{
28782
- type: NgModule,
28783
- args: [{
28784
- declarations: [
28785
- ExternalNavigationComponent,
28786
- NavigateToRouteComponent,
28787
- PageNotFoundComponent,
28788
- SafeHtmlPipe
28789
- ],
28790
- imports: [
28791
- CommonModule,
28792
- RouterModule,
28793
- MsalModule,
28794
- BrowserModule,
28795
- FormsModule
28796
- ],
28797
- exports: [
28798
- CommonModule,
28799
- RouterModule,
28800
- MsalModule,
28801
- ExternalNavigationComponent,
28802
- NavigateToRouteComponent,
28803
- PageNotFoundComponent,
28804
- SafeHtmlPipe
28805
- ]
28806
- }]
28807
- }] });
28808
-
28809
- const MockInventoryApiResponse = DEFAULT_INVENTORY_API_RESPONSE;
28810
-
28811
- const MockConfig = DEFAULT_CONFIGURATION;
28812
-
28813
- const MockProfile = DEFAULT_PROFILE;
28814
-
28815
- const MockCustomer = DEFAULT_CUSTOMER;
28816
-
28817
29751
  /**********************************************************************************************************************/
28818
29752
  class ConfigService {
28819
29753
  authConfig;
@@ -29007,6 +29941,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImpo
29007
29941
  args: [DOCUMENT]
29008
29942
  }] }, { type: ConfigService }, { type: i2$1.HttpClient }, { type: ClassLoggerService }] });
29009
29943
 
29944
+ const MockInventoryApiResponse = DEFAULT_INVENTORY_API_RESPONSE;
29945
+
29010
29946
  class InventoryApiService extends BaseApiService {
29011
29947
  useMockData = false;
29012
29948
  constructor(document, configService, httpClient, logger) {
@@ -29080,6 +30016,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImpo
29080
30016
  * Provides a wrapper for accessing the web storage API and synchronizing session storage across tabs/windows.
29081
30017
  */
29082
30018
  class LocalStorageService {
30019
+ static USER_DATA = 'user_data';
30020
+ static SYNC_KEYS = 'sync_keys';
29083
30021
  static syncListenerInitialized = false;
29084
30022
  syncKeys = [];
29085
30023
  initEvent = new Subject();
@@ -29111,7 +30049,7 @@ class LocalStorageService {
29111
30049
  }
29112
30050
  clearAllSessionsStorage() {
29113
30051
  this.clearInstanceSessionStorage();
29114
- localStorage.removeItem(DbKeys.SYNC_KEYS);
30052
+ localStorage.removeItem(LocalStorageService.SYNC_KEYS);
29115
30053
  localStorage.setItem('clearAllSessionsStorage', '_dummy');
29116
30054
  localStorage.removeItem('clearAllSessionsStorage');
29117
30055
  }
@@ -29122,23 +30060,23 @@ class LocalStorageService {
29122
30060
  clearLocalStorage() {
29123
30061
  localStorage.clear();
29124
30062
  }
29125
- saveSessionData(data, key = DbKeys.USER_DATA) {
30063
+ saveSessionData(data, key = LocalStorageService.USER_DATA) {
29126
30064
  this.testForInvalidKeys(key);
29127
30065
  this.removeFromSyncKeys(key);
29128
30066
  localStorage.removeItem(key);
29129
30067
  this.sessionStorageSetItem(key, data);
29130
30068
  }
29131
- saveSyncedSessionData(data, key = DbKeys.USER_DATA) {
30069
+ saveSyncedSessionData(data, key = LocalStorageService.USER_DATA) {
29132
30070
  this.testForInvalidKeys(key);
29133
30071
  localStorage.removeItem(key);
29134
30072
  this.addToSessionStorage(data, key);
29135
30073
  }
29136
- savePermanentData(data, key = DbKeys.USER_DATA) {
30074
+ savePermanentData(data, key = LocalStorageService.USER_DATA) {
29137
30075
  this.testForInvalidKeys(key);
29138
30076
  this.removeFromSessionStorage(key);
29139
30077
  this.localStorageSetItem(key, data);
29140
30078
  }
29141
- moveDataToSessionStorage(key = DbKeys.USER_DATA) {
30079
+ moveDataToSessionStorage(key = LocalStorageService.USER_DATA) {
29142
30080
  this.testForInvalidKeys(key);
29143
30081
  const data = this.getData(key);
29144
30082
  if (data == null) {
@@ -29146,7 +30084,7 @@ class LocalStorageService {
29146
30084
  }
29147
30085
  this.saveSessionData(data, key);
29148
30086
  }
29149
- moveDataToSyncedSessionStorage(key = DbKeys.USER_DATA) {
30087
+ moveDataToSyncedSessionStorage(key = LocalStorageService.USER_DATA) {
29150
30088
  this.testForInvalidKeys(key);
29151
30089
  const data = this.getData(key);
29152
30090
  if (data == null) {
@@ -29154,7 +30092,7 @@ class LocalStorageService {
29154
30092
  }
29155
30093
  this.saveSyncedSessionData(data, key);
29156
30094
  }
29157
- moveDataToPermanentStorage(key = DbKeys.USER_DATA) {
30095
+ moveDataToPermanentStorage(key = LocalStorageService.USER_DATA) {
29158
30096
  this.testForInvalidKeys(key);
29159
30097
  const data = this.getData(key);
29160
30098
  if (data == null) {
@@ -29162,14 +30100,14 @@ class LocalStorageService {
29162
30100
  }
29163
30101
  this.savePermanentData(data, key);
29164
30102
  }
29165
- exists(key = DbKeys.USER_DATA) {
30103
+ exists(key = LocalStorageService.USER_DATA) {
29166
30104
  let data = sessionStorage.getItem(key);
29167
30105
  if (data == null) {
29168
30106
  data = localStorage.getItem(key);
29169
30107
  }
29170
30108
  return data != null;
29171
30109
  }
29172
- getData(key = DbKeys.USER_DATA) {
30110
+ getData(key = LocalStorageService.USER_DATA) {
29173
30111
  this.testForInvalidKeys(key);
29174
30112
  let data = this.sessionStorageGetItem(key);
29175
30113
  if (data == '') {
@@ -29177,7 +30115,7 @@ class LocalStorageService {
29177
30115
  }
29178
30116
  return data;
29179
30117
  }
29180
- getDataObject(key = DbKeys.USER_DATA, isDateType = false) {
30118
+ getDataObject(key = LocalStorageService.USER_DATA, isDateType = false) {
29181
30119
  let data = this.getData(key);
29182
30120
  if (data != null) {
29183
30121
  if (isDateType) {
@@ -29189,7 +30127,7 @@ class LocalStorageService {
29189
30127
  return data;
29190
30128
  }
29191
30129
  }
29192
- deleteData(key = DbKeys.USER_DATA) {
30130
+ deleteData(key = LocalStorageService.USER_DATA) {
29193
30131
  this.testForInvalidKeys(key);
29194
30132
  this.removeFromSessionStorage(key);
29195
30133
  localStorage.removeItem(key);
@@ -29283,7 +30221,7 @@ class LocalStorageService {
29283
30221
  this.syncKeys = this.getSyncKeysFromStorage();
29284
30222
  }
29285
30223
  getSyncKeysFromStorage(defaultValue = []) {
29286
- const data = this.localStorageGetItem(DbKeys.SYNC_KEYS);
30224
+ const data = this.localStorageGetItem(LocalStorageService.SYNC_KEYS);
29287
30225
  if (data == null) {
29288
30226
  return defaultValue;
29289
30227
  }
@@ -29301,7 +30239,7 @@ class LocalStorageService {
29301
30239
  const storedSyncKeys = this.getSyncKeysFromStorage();
29302
30240
  if (!storedSyncKeys.some(x => x == key)) {
29303
30241
  storedSyncKeys.push(key);
29304
- this.localStorageSetItem(DbKeys.SYNC_KEYS, storedSyncKeys);
30242
+ this.localStorageSetItem(LocalStorageService.SYNC_KEYS, storedSyncKeys);
29305
30243
  }
29306
30244
  }
29307
30245
  removeFromSyncKeysBackup(key) {
@@ -29309,7 +30247,7 @@ class LocalStorageService {
29309
30247
  const index = storedSyncKeys.indexOf(key);
29310
30248
  if (index > -1) {
29311
30249
  storedSyncKeys.splice(index, 1);
29312
- this.localStorageSetItem(DbKeys.SYNC_KEYS, storedSyncKeys);
30250
+ this.localStorageSetItem(LocalStorageService.SYNC_KEYS, storedSyncKeys);
29313
30251
  }
29314
30252
  }
29315
30253
  addToSyncKeysHelper(key) {
@@ -29495,14 +30433,94 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImpo
29495
30433
  args: [DOCUMENT]
29496
30434
  }] }, { type: ConfigService }, { type: i2$1.HttpClient }, { type: ClassLoggerService }] });
29497
30435
 
30436
+ /**
30437
+ * Standalone Angular architecture for cwm-shared library
30438
+ * Export all standalone components, pipes, directives, and injectable services
30439
+ */
30440
+ // Components
30441
+
30442
+ /**
30443
+ * @deprecated Use standalone components directly instead
30444
+ *
30445
+ * This module is maintained for backwards compatibility only.
30446
+ * New applications should import standalone components directly:
30447
+ *
30448
+ * @example
30449
+ * import { PageNotFoundComponent, SafeHtmlPipe } from '@transcommerce/cwm-shared';
30450
+ *
30451
+ * @Component({
30452
+ * selector: 'app-root',
30453
+ * standalone: true,
30454
+ * imports: [PageNotFoundComponent, SafeHtmlPipe]
30455
+ * })
30456
+ */
30457
+ class CwmSharedModule {
30458
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: CwmSharedModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
30459
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.19", ngImport: i0, type: CwmSharedModule, imports: [CommonModule,
30460
+ RouterModule,
30461
+ MsalModule,
30462
+ BrowserModule,
30463
+ FormsModule,
30464
+ ExternalNavigationComponent,
30465
+ NavigateToRouteComponent,
30466
+ PageNotFoundComponent,
30467
+ SafeHtmlPipe], exports: [CommonModule,
30468
+ RouterModule,
30469
+ MsalModule,
30470
+ ExternalNavigationComponent,
30471
+ NavigateToRouteComponent,
30472
+ PageNotFoundComponent,
30473
+ SafeHtmlPipe] });
30474
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: CwmSharedModule, imports: [CommonModule,
30475
+ RouterModule,
30476
+ MsalModule,
30477
+ BrowserModule,
30478
+ FormsModule,
30479
+ ExternalNavigationComponent,
30480
+ NavigateToRouteComponent, CommonModule,
30481
+ RouterModule,
30482
+ MsalModule] });
30483
+ }
30484
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImport: i0, type: CwmSharedModule, decorators: [{
30485
+ type: NgModule,
30486
+ args: [{
30487
+ imports: [
30488
+ CommonModule,
30489
+ RouterModule,
30490
+ MsalModule,
30491
+ BrowserModule,
30492
+ FormsModule,
30493
+ ExternalNavigationComponent,
30494
+ NavigateToRouteComponent,
30495
+ PageNotFoundComponent,
30496
+ SafeHtmlPipe
30497
+ ],
30498
+ exports: [
30499
+ CommonModule,
30500
+ RouterModule,
30501
+ MsalModule,
30502
+ ExternalNavigationComponent,
30503
+ NavigateToRouteComponent,
30504
+ PageNotFoundComponent,
30505
+ SafeHtmlPipe
30506
+ ]
30507
+ }]
30508
+ }] });
30509
+
30510
+ const MockConfig = DEFAULT_CONFIGURATION;
30511
+
30512
+ const MockProfile = DEFAULT_PROFILE;
30513
+
30514
+ const MockCustomer = DEFAULT_CUSTOMER;
30515
+
29498
30516
  // ================================================
29499
30517
  // Export every type you want this module to expose
29500
30518
  // ================================================
29501
- // Components
30519
+ // Standalone Components (can be used independently)
29502
30520
 
29503
30521
  /**
29504
30522
  * Generated bundle index. Do not edit.
29505
30523
  */
29506
30524
 
29507
- export { API_CONFIG_SCHEMA, AUTH_CONFIG, AUTH_CONFIG_SCHEMA, BaseApiService, CAROUSEL_CONFIG_SCHEMA, CONFIGURATION_SCHEMA, Category, ClassLoggerService, ColorStyle, ConfigService, CoordinatesStyle, Customer, CwmSharedModule, DEFAULT_API_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACKCOLOR, DEFAULT_CAROUSEL_CONFIG, DEFAULT_CATEGORY_SLIDE, DEFAULT_COMPANY_NAME, DEFAULT_CONFIGURATION, DEFAULT_COORDINATES, DEFAULT_CUSTOMER, DEFAULT_FONT, DEFAULT_FORECOLOR, DEFAULT_IMAGE, DEFAULT_INVENTORY_API_RESPONSE, DEFAULT_MENU_BOARD_CONFIG, DEFAULT_PROFILE, DEFAULT_SIZE, DEFAULT_SLIDE, DEFAULT_SUBSCRIPTION_CONFIG, DEFAULT_TEXT_STYLE, DbKeys, EXAMPLE_CREATE_SUBSCRIPTION_REQUEST, ExternalNavigationComponent, FontStyle, ImageStyle, InventoryApiService, Justifications, LocalStorageService, LogService, MENU_BOARD_CONFIG_SCHEMA, MockConfig, MockCustomer, MockInventoryApiResponse, MockProfile, NavigateToRouteComponent, PageNotFoundComponent, Product, Profile, RgbaColorStyle, SLIDE_SCHEMA, SUBSCRIPTION_CONFIG_SCHEMA, SafeHtmlPipe, SizeStyle, Stack, SubscriptionApiService, TextStyle, TimeSpan, TimeSpanOverflowError, UserTypes, Utilities, adjustAlpha, cloneRGBA, createRGBA, cssStringToRGBA, darkenRGBA, decodeToken, doWithLock, hexToNamedColor, hexToRGBA, isTokenValid, isValidNamedColor, lightenRGBA, msalGuardConfigFactory, msalInstanceFactory, msalInterceptorConfigFactory, namedColorToHexColor, namedColorToRGBA, rgbaEquals, rgbaObjectToHex, rgbaToCssString, rgbaToHex, rgbaToNamedColor, waitFor };
30525
+ export { API_CONFIG_SCHEMA, AUTH_CONFIG, AUTH_CONFIG_SCHEMA, BaseApiService, CAROUSEL_CONFIG_SCHEMA, CONFIGURATION_SCHEMA, COORDINATES_STYLE_SCHEMA, Category, ClassLoggerService, ColorStyle, ConfigService, CoordinatesStyle, Customer, CwmSharedModule, DEFAULT_API_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACKCOLOR, DEFAULT_CAROUSEL_CONFIG, DEFAULT_CATEGORY_SLIDE, DEFAULT_COMPANY_NAME, DEFAULT_CONFIGURATION, DEFAULT_COORDINATES, DEFAULT_CUSTOMER, DEFAULT_FONT, DEFAULT_FOOTER_SLIDE, DEFAULT_FORECOLOR, DEFAULT_HEX_COLOR, DEFAULT_IMAGE_STYLE, DEFAULT_INVENTORY_API_RESPONSE, DEFAULT_JUSTIFICATION, DEFAULT_MENU_BOARD_CONFIG, DEFAULT_NAMED_COLOR, DEFAULT_NG_STYLE, DEFAULT_PROFILE, DEFAULT_RGBA_COLOR_STYLE, DEFAULT_SIZE_STYLE, DEFAULT_SLIDE, DEFAULT_SUBSCRIPTION_CONFIG, DEFAULT_TEXT_STYLE, EXAMPLE_CREATE_SUBSCRIPTION_REQUEST, ExternalNavigationComponent, FONT_STYLE_SCHEMA, FOOTER_SLIDE_SCHEMA, FontStyle, FooterSlide, HEX_COLOR_SCHEMA, IMAGE_STYLE_SCHEMA, ImageStyle, InventoryApiService, JUSTIFICATIONS_SCHEMA, Justifications, LocalStorageService, LogService, MENU_BOARD_CONFIG_SCHEMA, MockConfig, MockCustomer, MockInventoryApiResponse, MockProfile, NAMED_COLOR_MAP, NAMED_COLOR_SCHEMA, NG_STYLE_SCHEMA, NamedColors, NavigateToRouteComponent, PageNotFoundComponent, Product, Profile, RGBA_COLOR_STYLE_SCHEMA, RgbaColorStyle, SIZE_STYLE_SCHEMA, SLIDE_SCHEMA, SUBSCRIPTION_CONFIG_SCHEMA, SafeHtmlPipe, SizeStyle, Stack, SubscriptionApiService, TEXT_STYLE_SCHEMA, TextStyle, TimeSpan, TimeSpanOverflowError, UserTypes, Utilities, adjustAlpha, cloneRGBA, createRGBA, cssStringToRGBA, darkenRGBA, decodeToken, doWithLock, getNamedColors, hexToNamedColor, hexToRGBA, isNamedColor, isTokenValid, isValidNamedColor, lightenRGBA, msalGuardConfigFactory, msalInstanceFactory, msalInterceptorConfigFactory, namedColorToHex, namedColorToHexColor, namedColorToRGBA, rgbaEquals, rgbaObjectToHex, rgbaToCssString, rgbaToHex, rgbaToNamedColor, waitFor };
29508
30526
  //# sourceMappingURL=transcommerce-cwm-shared.mjs.map