@propbinder/mobile-design 0.2.90 → 0.2.92

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, effect, Injectable, inject, Input, Component, input, Directive, EventEmitter, HostListener, Output, PLATFORM_ID, ElementRef, output, CUSTOM_ELEMENTS_SCHEMA, TemplateRef, ContentChild, forwardRef, ViewChild, afterNextRender, ViewEncapsulation, model, createComponent, ChangeDetectionStrategy, NgZone, HostBinding } from '@angular/core';
2
+ import { signal, computed, effect, Injectable, inject, Input, Component, input, output, Directive, EventEmitter, HostListener, Output, PLATFORM_ID, ElementRef, CUSTOM_ELEMENTS_SCHEMA, TemplateRef, ContentChild, forwardRef, ViewChild, afterNextRender, ViewEncapsulation, model, createComponent, ChangeDetectionStrategy, NgZone, Pipe, HostBinding, InjectionToken } from '@angular/core';
3
3
  import * as i1$1 from '@angular/common';
4
4
  import { CommonModule, isPlatformBrowser } from '@angular/common';
5
5
  import * as i1$3 from '@angular/router';
@@ -689,39 +689,91 @@ class DsMobileGlassSpinnerComponent {
689
689
  */
690
690
  borderRadius = input(0, ...(ngDevMode ? [{ debugName: "borderRadius" }] : []));
691
691
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileGlassSpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
692
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: DsMobileGlassSpinnerComponent, isStandalone: true, selector: "ds-mobile-glass-spinner", inputs: { spinnerSize: { classPropertyName: "spinnerSize", publicName: "spinnerSize", isSignal: true, isRequired: false, transformFunction: null }, borderRadius: { classPropertyName: "borderRadius", publicName: "borderRadius", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
693
- <div
694
- class="glass-spinner-overlay"
695
- [style.border-radius.px]="borderRadius()"
696
- role="status"
697
- aria-live="polite"
698
- aria-label="Loading">
699
- <div
700
- class="spinner"
701
- [style.width.px]="spinnerSize()"
702
- [style.height.px]="spinnerSize()">
703
- </div>
704
- </div>
692
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: DsMobileGlassSpinnerComponent, isStandalone: true, selector: "ds-mobile-glass-spinner", inputs: { spinnerSize: { classPropertyName: "spinnerSize", publicName: "spinnerSize", isSignal: true, isRequired: false, transformFunction: null }, borderRadius: { classPropertyName: "borderRadius", publicName: "borderRadius", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
693
+ <div
694
+ class="glass-spinner-overlay"
695
+ [style.border-radius.px]="borderRadius()"
696
+ role="status"
697
+ aria-live="polite"
698
+ aria-label="Loading">
699
+ <div
700
+ class="spinner"
701
+ [style.width.px]="spinnerSize()"
702
+ [style.height.px]="spinnerSize()">
703
+ </div>
704
+ </div>
705
705
  `, isInline: true, styles: [":host{display:contents}.glass-spinner-overlay{position:absolute;inset:0;background:#fff6;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:9999;border-radius:inherit}.spinner{border:2px solid var(--color-border-neutral-secondary, #E5E5E5);border-top-color:var(--color-accent, #6B5FF5);border-radius:50%;animation:spin .6s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media (prefers-color-scheme: dark){.glass-spinner-overlay{background:#0000004d}.spinner{border-color:#fff3;border-top-color:var(--color-accent, #6B5FF5)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
706
706
  }
707
707
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileGlassSpinnerComponent, decorators: [{
708
708
  type: Component,
709
- args: [{ selector: 'ds-mobile-glass-spinner', standalone: true, imports: [CommonModule], template: `
710
- <div
711
- class="glass-spinner-overlay"
712
- [style.border-radius.px]="borderRadius()"
713
- role="status"
714
- aria-live="polite"
715
- aria-label="Loading">
716
- <div
717
- class="spinner"
718
- [style.width.px]="spinnerSize()"
719
- [style.height.px]="spinnerSize()">
720
- </div>
721
- </div>
709
+ args: [{ selector: 'ds-mobile-glass-spinner', standalone: true, imports: [CommonModule], template: `
710
+ <div
711
+ class="glass-spinner-overlay"
712
+ [style.border-radius.px]="borderRadius()"
713
+ role="status"
714
+ aria-live="polite"
715
+ aria-label="Loading">
716
+ <div
717
+ class="spinner"
718
+ [style.width.px]="spinnerSize()"
719
+ [style.height.px]="spinnerSize()">
720
+ </div>
721
+ </div>
722
722
  `, styles: [":host{display:contents}.glass-spinner-overlay{position:absolute;inset:0;background:#fff6;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:9999;border-radius:inherit}.spinner{border:2px solid var(--color-border-neutral-secondary, #E5E5E5);border-top-color:var(--color-accent, #6B5FF5);border-radius:50%;animation:spin .6s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media (prefers-color-scheme: dark){.glass-spinner-overlay{background:#0000004d}.spinner{border-color:#fff3;border-top-color:var(--color-accent, #6B5FF5)}}\n"] }]
723
723
  }], propDecorators: { spinnerSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "spinnerSize", required: false }] }], borderRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderRadius", required: false }] }] } });
724
724
 
725
+ class DsMobileCountBadgeComponent {
726
+ count = input.required(...(ngDevMode ? [{ debugName: "count" }] : []));
727
+ variant = input('accent', ...(ngDevMode ? [{ debugName: "variant" }] : []));
728
+ display = computed(() => {
729
+ const n = this.count();
730
+ return n > 99 ? '99+' : `${n}`;
731
+ }, ...(ngDevMode ? [{ debugName: "display" }] : []));
732
+ label = computed(() => `${this.count()} notifikationer`, ...(ngDevMode ? [{ debugName: "label" }] : []));
733
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileCountBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
734
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: DsMobileCountBadgeComponent, isStandalone: true, selector: "ds-mobile-count-badge", inputs: { count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: true, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "'count-badge count-badge--' + variant()", "attr.aria-label": "label()" } }, ngImport: i0, template: `{{ display() }}`, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:16px;padding:0 6px;border-radius:10px;font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:var(--font-size-xs);line-height:1;color:var(--color-on-accent, #ffffff);white-space:nowrap;box-sizing:border-box}:host(.count-badge--accent){background:var(--color-accent, #6B5FF5)}:host(.count-badge--notification){width:16px;height:16px;min-width:16px;padding:0;border-radius:4px;background:var(--content-feedback-bold-red, #b91c1c)}:host(.count-badge--muted){background:rgba(var(--header-content-color-rgb, 255, 255, 255),.2);color:var(--header-content-color, white)}\n"] });
735
+ }
736
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileCountBadgeComponent, decorators: [{
737
+ type: Component,
738
+ args: [{ selector: 'ds-mobile-count-badge', standalone: true, host: {
739
+ '[class]': "'count-badge count-badge--' + variant()",
740
+ '[attr.aria-label]': "label()",
741
+ }, template: `{{ display() }}`, styles: [":host{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:16px;padding:0 6px;border-radius:10px;font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:var(--font-size-xs);line-height:1;color:var(--color-on-accent, #ffffff);white-space:nowrap;box-sizing:border-box}:host(.count-badge--accent){background:var(--color-accent, #6B5FF5)}:host(.count-badge--notification){width:16px;height:16px;min-width:16px;padding:0;border-radius:4px;background:var(--content-feedback-bold-red, #b91c1c)}:host(.count-badge--muted){background:rgba(var(--header-content-color-rgb, 255, 255, 255),.2);color:var(--header-content-color, white)}\n"] }]
742
+ }], propDecorators: { count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }] } });
743
+
744
+ class DsMobileNotificationButtonComponent {
745
+ count = input(0, ...(ngDevMode ? [{ debugName: "count" }] : []));
746
+ clicked = output();
747
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
748
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileNotificationButtonComponent, isStandalone: true, selector: "ds-mobile-notification-button", inputs: { count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, ngImport: i0, template: `
749
+ <ds-icon-button
750
+ icon="remixNotificationLine"
751
+ variant="secondary"
752
+ size="md"
753
+ (clicked)="clicked.emit()"
754
+ [ariaLabel]="'Notifikationer'"
755
+ />
756
+ @if (count() > 0) {
757
+ <ds-mobile-count-badge [count]="count()" variant="notification" />
758
+ }
759
+ `, isInline: true, styles: [":host{position:relative;display:inline-flex;align-items:center;justify-content:center}ds-icon-button{--text-color-default-secondary: var(--color-header-content, white);--text-color-default-primary: var(--color-header-content, white);--color-background-neutral-secondary-hover: rgba(var(--color-header-content-rgb, 255, 255, 255), .1);--color-background-neutral-secondary: transparent}ds-icon-button::ng-deep button{border-radius:50%!important}ds-mobile-count-badge{position:absolute;top:0;right:0;pointer-events:none}\n"], dependencies: [{ kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsMobileCountBadgeComponent, selector: "ds-mobile-count-badge", inputs: ["count", "variant"] }] });
760
+ }
761
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationButtonComponent, decorators: [{
762
+ type: Component,
763
+ args: [{ selector: 'ds-mobile-notification-button', standalone: true, imports: [DsIconButtonComponent, DsMobileCountBadgeComponent], template: `
764
+ <ds-icon-button
765
+ icon="remixNotificationLine"
766
+ variant="secondary"
767
+ size="md"
768
+ (clicked)="clicked.emit()"
769
+ [ariaLabel]="'Notifikationer'"
770
+ />
771
+ @if (count() > 0) {
772
+ <ds-mobile-count-badge [count]="count()" variant="notification" />
773
+ }
774
+ `, styles: [":host{position:relative;display:inline-flex;align-items:center;justify-content:center}ds-icon-button{--text-color-default-secondary: var(--color-header-content, white);--text-color-default-primary: var(--color-header-content, white);--color-background-neutral-secondary-hover: rgba(var(--color-header-content-rgb, 255, 255, 255), .1);--color-background-neutral-secondary: transparent}ds-icon-button::ng-deep button{border-radius:50%!important}ds-mobile-count-badge{position:absolute;top:0;right:0;pointer-events:none}\n"] }]
775
+ }], propDecorators: { count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], clicked: [{ type: i0.Output, args: ["clicked"] }] } });
776
+
725
777
  /**
726
778
  * MobilePageBase
727
779
  *
@@ -5835,6 +5887,8 @@ class DsMobilePageMainComponent extends MobilePageBase {
5835
5887
  avatarInitials = input('U', ...(ngDevMode ? [{ debugName: "avatarInitials" }] : []));
5836
5888
  avatarSrc = input('', ...(ngDevMode ? [{ debugName: "avatarSrc" }] : []));
5837
5889
  avatarIconName = input('remixUser3Line', ...(ngDevMode ? [{ debugName: "avatarIconName" }] : []));
5890
+ // Inputs - Notifications
5891
+ notificationCount = input(0, ...(ngDevMode ? [{ debugName: "notificationCount" }] : []));
5838
5892
  // Inputs - Features
5839
5893
  showRefresh = input(true, ...(ngDevMode ? [{ debugName: "showRefresh" }] : []));
5840
5894
  showCondensedHeader = input(true, ...(ngDevMode ? [{ debugName: "showCondensedHeader" }] : []));
@@ -5885,6 +5939,7 @@ class DsMobilePageMainComponent extends MobilePageBase {
5885
5939
  */
5886
5940
  profileMenuItems = input(undefined, ...(ngDevMode ? [{ debugName: "profileMenuItems" }] : []));
5887
5941
  // Outputs
5942
+ notificationClick = output();
5888
5943
  avatarClick = output();
5889
5944
  /**
5890
5945
  * Emitted when a profile menu action is selected.
@@ -6074,7 +6129,7 @@ class DsMobilePageMainComponent extends MobilePageBase {
6074
6129
  this.refresh.emit(event);
6075
6130
  }
6076
6131
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobilePageMainComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
6077
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobilePageMainComponent, isStandalone: true, selector: "ds-mobile-page-main", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, headerTitle: { classPropertyName: "headerTitle", publicName: "headerTitle", isSignal: true, isRequired: false, transformFunction: null }, headerSubtitle: { classPropertyName: "headerSubtitle", publicName: "headerSubtitle", isSignal: true, isRequired: false, transformFunction: null }, firstEntry: { classPropertyName: "firstEntry", publicName: "firstEntry", isSignal: true, isRequired: false, transformFunction: null }, avatarType: { classPropertyName: "avatarType", publicName: "avatarType", isSignal: true, isRequired: false, transformFunction: null }, avatarInitials: { classPropertyName: "avatarInitials", publicName: "avatarInitials", isSignal: true, isRequired: false, transformFunction: null }, avatarSrc: { classPropertyName: "avatarSrc", publicName: "avatarSrc", isSignal: true, isRequired: false, transformFunction: null }, avatarIconName: { classPropertyName: "avatarIconName", publicName: "avatarIconName", isSignal: true, isRequired: false, transformFunction: null }, showRefresh: { classPropertyName: "showRefresh", publicName: "showRefresh", isSignal: true, isRequired: false, transformFunction: null }, showCondensedHeader: { classPropertyName: "showCondensedHeader", publicName: "showCondensedHeader", isSignal: true, isRequired: false, transformFunction: null }, scrollThreshold: { classPropertyName: "scrollThreshold", publicName: "scrollThreshold", isSignal: true, isRequired: false, transformFunction: null }, headerFadeDistance: { classPropertyName: "headerFadeDistance", publicName: "headerFadeDistance", isSignal: true, isRequired: false, transformFunction: null }, contentPadding: { classPropertyName: "contentPadding", publicName: "contentPadding", isSignal: true, isRequired: false, transformFunction: null }, profileMenuItems: { classPropertyName: "profileMenuItems", publicName: "profileMenuItems", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { avatarClick: "avatarClick", profileActionSelected: "profileActionSelected", refresh: "refresh", scroll: "scroll" }, host: { properties: { "style.--content-wrapper-padding": "contentPadding()" } }, viewQueries: [{ propertyName: "ionContent", first: true, predicate: IonContent, descendants: true }], usesInheritance: true, ngImport: i0, template: `
6132
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobilePageMainComponent, isStandalone: true, selector: "ds-mobile-page-main", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, headerTitle: { classPropertyName: "headerTitle", publicName: "headerTitle", isSignal: true, isRequired: false, transformFunction: null }, headerSubtitle: { classPropertyName: "headerSubtitle", publicName: "headerSubtitle", isSignal: true, isRequired: false, transformFunction: null }, firstEntry: { classPropertyName: "firstEntry", publicName: "firstEntry", isSignal: true, isRequired: false, transformFunction: null }, avatarType: { classPropertyName: "avatarType", publicName: "avatarType", isSignal: true, isRequired: false, transformFunction: null }, avatarInitials: { classPropertyName: "avatarInitials", publicName: "avatarInitials", isSignal: true, isRequired: false, transformFunction: null }, avatarSrc: { classPropertyName: "avatarSrc", publicName: "avatarSrc", isSignal: true, isRequired: false, transformFunction: null }, avatarIconName: { classPropertyName: "avatarIconName", publicName: "avatarIconName", isSignal: true, isRequired: false, transformFunction: null }, notificationCount: { classPropertyName: "notificationCount", publicName: "notificationCount", isSignal: true, isRequired: false, transformFunction: null }, showRefresh: { classPropertyName: "showRefresh", publicName: "showRefresh", isSignal: true, isRequired: false, transformFunction: null }, showCondensedHeader: { classPropertyName: "showCondensedHeader", publicName: "showCondensedHeader", isSignal: true, isRequired: false, transformFunction: null }, scrollThreshold: { classPropertyName: "scrollThreshold", publicName: "scrollThreshold", isSignal: true, isRequired: false, transformFunction: null }, headerFadeDistance: { classPropertyName: "headerFadeDistance", publicName: "headerFadeDistance", isSignal: true, isRequired: false, transformFunction: null }, contentPadding: { classPropertyName: "contentPadding", publicName: "contentPadding", isSignal: true, isRequired: false, transformFunction: null }, profileMenuItems: { classPropertyName: "profileMenuItems", publicName: "profileMenuItems", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { notificationClick: "notificationClick", avatarClick: "avatarClick", profileActionSelected: "profileActionSelected", refresh: "refresh", scroll: "scroll" }, host: { properties: { "style.--content-wrapper-padding": "contentPadding()" } }, viewQueries: [{ propertyName: "ionContent", first: true, predicate: IonContent, descendants: true }], usesInheritance: true, ngImport: i0, template: `
6078
6133
  <!-- Fixed header at top -->
6079
6134
  <ion-header>
6080
6135
  <ion-toolbar>
@@ -6085,8 +6140,12 @@ class DsMobilePageMainComponent extends MobilePageBase {
6085
6140
  <!-- Title - fades in on scroll -->
6086
6141
  <ion-title class="header-main__title">{{ title() }}</ion-title>
6087
6142
 
6088
- <!-- Avatar -->
6143
+ <!-- Notification + Avatar -->
6089
6144
  <div class="header-main__actions">
6145
+ <ds-mobile-notification-button
6146
+ [count]="notificationCount()"
6147
+ (clicked)="notificationClick.emit()"
6148
+ />
6090
6149
  <ds-avatar
6091
6150
  [size]="'md'"
6092
6151
  [type]="avatarType()"
@@ -6151,11 +6210,11 @@ class DsMobilePageMainComponent extends MobilePageBase {
6151
6210
  </div>
6152
6211
  </div>
6153
6212
  </ion-content>
6154
- `, isInline: true, styles: [":host{display:flex;flex-direction:column;align-items:center;height:100%;background:var(--color-header-surface);width:100%}:host ion-header{background:var(--color-header-surface);box-shadow:none;height:72px;min-height:72px;margin-top:var(--app-header-top-offset)}:host ion-header ion-toolbar{--background: var(--color-header-surface);--border-width: 0;--box-shadow: none;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 72px;height:72px;min-height:72px;padding:0}@media (min-width: 768px){:host ion-header{height:88px;min-height:88px}:host ion-header ion-toolbar{--min-height: 88px;height:88px;min-height:88px}}@media (min-width: 768px){:host ion-header{display:none;height:auto}}:host .header-main,:host .header-details,.header-details{display:flex;align-items:center;justify-content:space-between;background:var(--color-header-surface);position:relative}:host .header-main__title,:host .header-details .header-title,.header-details .header-title{position:absolute;left:50%;transform:translate(-50%);font-size:var(--font-size-base);font-weight:600;color:var(--color-header-content);margin:0;padding:0;--color: var(--color-header-content)}.header-details .header-title{transform:translate(-50%) translateY(-100%);opacity:0!important;pointer-events:none;transition:transform .2s ease,opacity .2s ease!important}.header-scrolled .header-details .header-title{opacity:1!important;pointer-events:auto;transform:translate(-50%) translateY(0)}:host .header-details .back-button,.header-details .back-button{background:none;border:none;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--color-header-content);transition:opacity var(--transition-duration-fast, .2s) var(--ease-smooth, ease);z-index:10;position:relative}:host .header-details .back-button:hover,.header-details .back-button:hover{opacity:.8}:host .header-details .back-button:active,.header-details .back-button:active{opacity:.6}:host ion-content{--background: var(--color-header-surface);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;border-radius:24px 24px 0 0;overflow:hidden}:host ion-content::part(scroll){display:flex;flex-direction:column;min-height:100%;-webkit-overflow-scrolling:touch}.plt-ios :host ion-content{--background: var(--color-background-neutral-primary)}@media (min-width: 768px){:host ion-content{border-radius:24px 24px 0 0}}:host ion-header[collapse=condense]{display:none}@media (min-width: 768px){:host ion-header[collapse=condense]{display:none}}:host ion-refresher{z-index:0}:host ion-refresher-content{--color: var(--color-header-content)}:host .content-wrapper{width:100%;position:relative;z-index:20;flex:1;display:flex;flex-direction:column;background:var(--color-background-neutral-primary);border-radius:24px 24px 0 0;overflow:visible;transform:translateZ(0);will-change:transform;isolation:isolate;box-shadow:0 300px 0 0 var(--color-background-neutral-primary);padding-top:0;padding-left:var(--content-wrapper-padding, 20px);padding-right:var(--content-wrapper-padding, 20px);padding-bottom:calc(var(--mobile-content-spacing) + var(--mobile-tab-bar-height) + var(--app-safe-bottom, 0px))}:host .content-inner{max-width:640px;margin:0 auto;width:100%}@media (min-width: 768px){:host .content-wrapper{padding-top:0;padding-left:var(--content-wrapper-padding, 20px);padding-right:var(--content-wrapper-padding, 20px)}}:host .header-expandable{background:var(--color-header-surface);padding:32px 20px 24px;color:var(--header-content-color, white);position:sticky;top:0;z-index:5;transition:opacity .1s ease-out,transform .1s ease-out;flex:0 0 auto;min-height:-moz-min-content;min-height:min-content}:host .header-expandable-inner{display:flex;flex-direction:column;gap:20px;max-width:640px;margin:0 auto}:host .header-expandable__text{margin-bottom:0;gap:4px;display:flex;flex-direction:column}:host .header-expandable__title{font-size:var(--font-size-2xl);font-weight:600;color:var(--header-content-color, white);margin:0}:host .header-expandable__subtitle{font-size:var(--font-size-sm);font-weight:400;color:var(--header-content-color, white);opacity:.85;margin:0}@media (min-width: 768px){:host .header-expandable{padding:48px 20px 32px}:host .header-expandable__title{font-size:var(--font-size-3xl)}:host .header-expandable__subtitle{font-size:var(--font-size-base)}}@media (min-width: 992px){:host .header-expandable{padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){:host .header-expandable{padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){:host .header-expandable{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){:host .header-expandable{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}\n", ".header-main{padding:0 20px;height:72px}.header-main__title{transform:translate(-50%) translateY(-100%);opacity:0;transition:transform .6s ease,opacity .6s ease;padding:0;--color: var(--color-header-content)}.header-scrolled .header-main__title{opacity:1;transform:translate(-50%) translateY(0)}.header-main__actions{display:flex;align-items:center;gap:8px}.header-main__actions ds-avatar{cursor:pointer;-webkit-tap-highlight-color:transparent}@keyframes logoSlideIn{0%{transform:translateY(-200%);scale:.75;opacity:0}to{transform:translateY(0);scale:1;opacity:1}}.logo--first-entry{animation:logoSlideIn .75s var(--spring-curve-bouncy) .7s both}@media (min-width: 768px){.header-main{padding:16px 24px;height:88px}.header-main__title{display:none}}ion-refresher{--color: var(--color-header-content)}ion-refresher-content{--color: var(--color-header-content)}ion-refresher-content::part(spinner){color:var(--color-header-content)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "mode", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }, { kind: "component", type: DsMobileGlassSpinnerComponent, selector: "ds-mobile-glass-spinner", inputs: ["spinnerSize", "borderRadius"] }] });
6213
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;align-items:center;height:100%;background:var(--color-header-surface);width:100%}:host ion-header{background:var(--color-header-surface);box-shadow:none;height:72px;min-height:72px;margin-top:var(--app-header-top-offset)}:host ion-header ion-toolbar{--background: var(--color-header-surface);--border-width: 0;--box-shadow: none;--padding-top: 0;--padding-bottom: 0;--padding-start: 0;--padding-end: 0;--min-height: 72px;height:72px;min-height:72px;padding:0}@media (min-width: 768px){:host ion-header{height:88px;min-height:88px}:host ion-header ion-toolbar{--min-height: 88px;height:88px;min-height:88px}}@media (min-width: 768px){:host ion-header{display:none;height:auto}}:host .header-main,:host .header-details,.header-details{display:flex;align-items:center;justify-content:space-between;background:var(--color-header-surface);position:relative}:host .header-main__title,:host .header-details .header-title,.header-details .header-title{position:absolute;left:50%;transform:translate(-50%);font-size:var(--font-size-base);font-weight:600;color:var(--color-header-content);margin:0;padding:0;--color: var(--color-header-content)}.header-details .header-title{transform:translate(-50%) translateY(-100%);opacity:0!important;pointer-events:none;transition:transform .2s ease,opacity .2s ease!important}.header-scrolled .header-details .header-title{opacity:1!important;pointer-events:auto;transform:translate(-50%) translateY(0)}:host .header-details .back-button,.header-details .back-button{background:none;border:none;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--color-header-content);transition:opacity var(--transition-duration-fast, .2s) var(--ease-smooth, ease);z-index:10;position:relative}:host .header-details .back-button:hover,.header-details .back-button:hover{opacity:.8}:host .header-details .back-button:active,.header-details .back-button:active{opacity:.6}:host ion-content{--background: var(--color-header-surface);--padding-top: 0;--padding-start: 0;--padding-end: 0;--padding-bottom: 0;border-radius:24px 24px 0 0;overflow:hidden}:host ion-content::part(scroll){display:flex;flex-direction:column;min-height:100%;-webkit-overflow-scrolling:touch}.plt-ios :host ion-content{--background: var(--color-background-neutral-primary)}@media (min-width: 768px){:host ion-content{border-radius:24px 24px 0 0}}:host ion-header[collapse=condense]{display:none}@media (min-width: 768px){:host ion-header[collapse=condense]{display:none}}:host ion-refresher{z-index:0}:host ion-refresher-content{--color: var(--color-header-content)}:host .content-wrapper{width:100%;position:relative;z-index:20;flex:1;display:flex;flex-direction:column;background:var(--color-background-neutral-primary);border-radius:24px 24px 0 0;overflow:visible;transform:translateZ(0);will-change:transform;isolation:isolate;box-shadow:0 300px 0 0 var(--color-background-neutral-primary);padding-top:0;padding-left:var(--content-wrapper-padding, 20px);padding-right:var(--content-wrapper-padding, 20px);padding-bottom:calc(var(--mobile-content-spacing) + var(--mobile-tab-bar-height) + var(--app-safe-bottom, 0px))}:host .content-inner{max-width:640px;margin:0 auto;width:100%}@media (min-width: 768px){:host .content-wrapper{padding-top:0;padding-left:var(--content-wrapper-padding, 20px);padding-right:var(--content-wrapper-padding, 20px)}}:host .header-expandable{background:var(--color-header-surface);padding:32px 20px 24px;color:var(--header-content-color, white);position:sticky;top:0;z-index:5;transition:opacity .1s ease-out,transform .1s ease-out;flex:0 0 auto;min-height:-moz-min-content;min-height:min-content}:host .header-expandable-inner{display:flex;flex-direction:column;gap:20px;max-width:640px;margin:0 auto}:host .header-expandable__text{margin-bottom:0;gap:4px;display:flex;flex-direction:column}:host .header-expandable__title{font-size:var(--font-size-2xl);font-weight:600;color:var(--header-content-color, white);margin:0}:host .header-expandable__subtitle{font-size:var(--font-size-sm);font-weight:400;color:var(--header-content-color, white);opacity:.85;margin:0}@media (min-width: 768px){:host .header-expandable{padding:48px 20px 32px}:host .header-expandable__title{font-size:var(--font-size-3xl)}:host .header-expandable__subtitle{font-size:var(--font-size-base)}}@media (min-width: 992px){:host .header-expandable{padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg)}}@media (min-width: 1440px){:host .header-expandable{padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){:host .header-expandable{padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){:host .header-expandable{padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}\n", ".header-main{padding:0 20px;height:72px}.header-main__title{transform:translate(-50%) translateY(-100%);opacity:0;transition:transform .6s ease,opacity .6s ease;padding:0;--color: var(--color-header-content)}.header-scrolled .header-main__title{opacity:1;transform:translate(-50%) translateY(0)}.header-main__actions{display:flex;align-items:center;gap:8px}.header-main__actions ds-avatar{cursor:pointer;-webkit-tap-highlight-color:transparent}@keyframes logoSlideIn{0%{transform:translateY(-200%);scale:.75;opacity:0}to{transform:translateY(0);scale:1;opacity:1}}.logo--first-entry{animation:logoSlideIn .75s var(--spring-curve-bouncy) .7s both}@media (min-width: 768px){.header-main{padding:16px 24px;height:88px}.header-main__title{display:none}}ion-refresher{--color: var(--color-header-content)}ion-refresher-content{--color: var(--color-header-content)}ion-refresher-content::part(spinner){color:var(--color-header-content)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonRefresher, selector: "ion-refresher", inputs: ["closeDuration", "disabled", "mode", "pullFactor", "pullMax", "pullMin", "snapbackDuration"] }, { kind: "component", type: IonRefresherContent, selector: "ion-refresher-content", inputs: ["pullingIcon", "pullingText", "refreshingSpinner", "refreshingText"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }, { kind: "component", type: DsMobileGlassSpinnerComponent, selector: "ds-mobile-glass-spinner", inputs: ["spinnerSize", "borderRadius"] }, { kind: "component", type: DsMobileNotificationButtonComponent, selector: "ds-mobile-notification-button", inputs: ["count"], outputs: ["clicked"] }] });
6155
6214
  }
6156
6215
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobilePageMainComponent, decorators: [{
6157
6216
  type: Component,
6158
- args: [{ selector: 'ds-mobile-page-main', standalone: true, imports: [CommonModule, IonHeader, IonToolbar, IonTitle, IonContent, IonRefresher, IonRefresherContent, DsAvatarComponent, DsLogoComponent, DsMobileGlassSpinnerComponent], host: {
6217
+ args: [{ selector: 'ds-mobile-page-main', standalone: true, imports: [CommonModule, IonHeader, IonToolbar, IonTitle, IonContent, IonRefresher, IonRefresherContent, DsAvatarComponent, DsLogoComponent, DsMobileGlassSpinnerComponent, DsMobileNotificationButtonComponent], host: {
6159
6218
  '[style.--content-wrapper-padding]': 'contentPadding()'
6160
6219
  }, template: `
6161
6220
  <!-- Fixed header at top -->
@@ -6168,8 +6227,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
6168
6227
  <!-- Title - fades in on scroll -->
6169
6228
  <ion-title class="header-main__title">{{ title() }}</ion-title>
6170
6229
 
6171
- <!-- Avatar -->
6230
+ <!-- Notification + Avatar -->
6172
6231
  <div class="header-main__actions">
6232
+ <ds-mobile-notification-button
6233
+ [count]="notificationCount()"
6234
+ (clicked)="notificationClick.emit()"
6235
+ />
6173
6236
  <ds-avatar
6174
6237
  [size]="'md'"
6175
6238
  [type]="avatarType()"
@@ -6238,7 +6301,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
6238
6301
  }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { ionContent: [{
6239
6302
  type: ViewChild,
6240
6303
  args: [IonContent]
6241
- }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], headerTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerTitle", required: false }] }], headerSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerSubtitle", required: false }] }], firstEntry: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstEntry", required: false }] }], avatarType: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarType", required: false }] }], avatarInitials: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarInitials", required: false }] }], avatarSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarSrc", required: false }] }], avatarIconName: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarIconName", required: false }] }], showRefresh: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRefresh", required: false }] }], showCondensedHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCondensedHeader", required: false }] }], scrollThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollThreshold", required: false }] }], headerFadeDistance: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerFadeDistance", required: false }] }], contentPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentPadding", required: false }] }], profileMenuItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "profileMenuItems", required: false }] }], avatarClick: [{ type: i0.Output, args: ["avatarClick"] }], profileActionSelected: [{ type: i0.Output, args: ["profileActionSelected"] }], refresh: [{ type: i0.Output, args: ["refresh"] }], scroll: [{ type: i0.Output, args: ["scroll"] }] } });
6304
+ }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], headerTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerTitle", required: false }] }], headerSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerSubtitle", required: false }] }], firstEntry: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstEntry", required: false }] }], avatarType: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarType", required: false }] }], avatarInitials: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarInitials", required: false }] }], avatarSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarSrc", required: false }] }], avatarIconName: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarIconName", required: false }] }], notificationCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "notificationCount", required: false }] }], showRefresh: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRefresh", required: false }] }], showCondensedHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCondensedHeader", required: false }] }], scrollThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollThreshold", required: false }] }], headerFadeDistance: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerFadeDistance", required: false }] }], contentPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentPadding", required: false }] }], profileMenuItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "profileMenuItems", required: false }] }], notificationClick: [{ type: i0.Output, args: ["notificationClick"] }], avatarClick: [{ type: i0.Output, args: ["avatarClick"] }], profileActionSelected: [{ type: i0.Output, args: ["profileActionSelected"] }], refresh: [{ type: i0.Output, args: ["refresh"] }], scroll: [{ type: i0.Output, args: ["scroll"] }] } });
6242
6305
 
6243
6306
  /**
6244
6307
  * DsMobileInlineTabsComponent
@@ -6282,6 +6345,7 @@ class DsMobileInlineTabsComponent {
6282
6345
  <button
6283
6346
  class="filter-tab"
6284
6347
  [class.active]="activeTab() === tab.id"
6348
+ [class.has-envelope]="tab.badgeType === 'envelope'"
6285
6349
  (click)="handleTabClick(tab.id)"
6286
6350
  [attr.aria-label]="tab.label"
6287
6351
  [attr.aria-selected]="activeTab() === tab.id">
@@ -6296,21 +6360,22 @@ class DsMobileInlineTabsComponent {
6296
6360
  }
6297
6361
  </div>
6298
6362
  } @else if (tab.badge && tab.badge > 0) {
6299
- <span class="tab-badge">{{ tab.badge }}</span>
6363
+ <ds-mobile-count-badge [count]="tab.badge" variant="muted" />
6300
6364
  }
6301
6365
  </button>
6302
6366
  }
6303
6367
  </div>
6304
- `, isInline: true, styles: [".filter-tabs{display:flex;align-items:center;gap:8px;flex-wrap:wrap;height:40px}.filter-tab{min-width:48px;justify-content:center;padding:0 12px;height:32px;border-radius:20px;background:transparent;border:none;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:rgba(var(--header-content-color-rgb, 255, 255, 255),.6);cursor:pointer;transition:all .2s ease;display:flex;align-items:center;gap:6px;white-space:nowrap}.filter-tab.active{background:var(--color-header-accent, #5d5fef);color:var(--color-on-header-accent, white)}.filter-tab:hover:not(.active){background:rgba(var(--header-content-color-rgb, 255, 255, 255),.1)}.tab-badge{min-width:24px;height:16px;padding:0 6px;border-radius:10px;background:rgba(var(--header-content-color-rgb, 255, 255, 255),.2);color:var(--header-content-color, white);font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:600;display:flex;align-items:center;justify-content:center;line-height:1}.filter-tab.active .tab-badge{background:rgba(var(--color-on-header-accent-rgb, 255, 255, 255),.3);color:var(--color-on-header-accent, white)}.envelope-wrapper{position:relative;display:inline-flex;align-items:center;justify-content:center;margin-left:4px}.envelope-badge{position:absolute;top:-3px;right:-5px;background-color:#ff3b30;color:#fff;font-size:10px;font-weight:700;border-radius:9999px;min-width:12px;height:12px;display:flex;align-items:center;justify-content:center;padding:0;line-height:1;box-shadow:0 1px 2px #00000026;z-index:10}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }] });
6368
+ `, isInline: true, styles: [".filter-tabs{display:flex;align-items:center;gap:8px;flex-wrap:wrap;height:40px}.filter-tab{min-width:48px;justify-content:center;padding:0 12px;height:32px;border-radius:20px;background:transparent;border:none;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:rgba(var(--header-content-color-rgb, 255, 255, 255),.6);cursor:pointer;transition:all .2s ease;display:flex;align-items:center;gap:6px;white-space:nowrap}.filter-tab.active{background:var(--color-header-accent, #5d5fef);color:var(--color-on-header-accent, white)}.filter-tab:hover:not(.active){background:rgba(var(--header-content-color-rgb, 255, 255, 255),.1)}.filter-tab.active ds-mobile-count-badge{--header-content-color-rgb: var(--color-on-header-accent-rgb, 255, 255, 255);--header-content-color: var(--color-on-header-accent, white)}.filter-tab.has-envelope{gap:0}.envelope-wrapper{position:relative;display:inline-flex;align-items:center;justify-content:center;margin-left:4px}.envelope-badge{position:absolute;top:-3px;right:-5px;background-color:#ff3b30;color:#fff;font-size:10px;font-weight:700;border-radius:9999px;min-width:12px;height:12px;display:flex;align-items:center;justify-content:center;padding:0;line-height:1;box-shadow:0 1px 2px #00000026;z-index:10}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobileCountBadgeComponent, selector: "ds-mobile-count-badge", inputs: ["count", "variant"] }] });
6305
6369
  }
6306
6370
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileInlineTabsComponent, decorators: [{
6307
6371
  type: Component,
6308
- args: [{ selector: 'ds-mobile-inline-tabs', standalone: true, imports: [CommonModule, DsIconComponent], template: `
6372
+ args: [{ selector: 'ds-mobile-inline-tabs', standalone: true, imports: [CommonModule, DsIconComponent, DsMobileCountBadgeComponent], template: `
6309
6373
  <div class="filter-tabs">
6310
6374
  @for (tab of tabs(); track tab.id) {
6311
6375
  <button
6312
6376
  class="filter-tab"
6313
6377
  [class.active]="activeTab() === tab.id"
6378
+ [class.has-envelope]="tab.badgeType === 'envelope'"
6314
6379
  (click)="handleTabClick(tab.id)"
6315
6380
  [attr.aria-label]="tab.label"
6316
6381
  [attr.aria-selected]="activeTab() === tab.id">
@@ -6325,12 +6390,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
6325
6390
  }
6326
6391
  </div>
6327
6392
  } @else if (tab.badge && tab.badge > 0) {
6328
- <span class="tab-badge">{{ tab.badge }}</span>
6393
+ <ds-mobile-count-badge [count]="tab.badge" variant="muted" />
6329
6394
  }
6330
6395
  </button>
6331
6396
  }
6332
6397
  </div>
6333
- `, styles: [".filter-tabs{display:flex;align-items:center;gap:8px;flex-wrap:wrap;height:40px}.filter-tab{min-width:48px;justify-content:center;padding:0 12px;height:32px;border-radius:20px;background:transparent;border:none;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:rgba(var(--header-content-color-rgb, 255, 255, 255),.6);cursor:pointer;transition:all .2s ease;display:flex;align-items:center;gap:6px;white-space:nowrap}.filter-tab.active{background:var(--color-header-accent, #5d5fef);color:var(--color-on-header-accent, white)}.filter-tab:hover:not(.active){background:rgba(var(--header-content-color-rgb, 255, 255, 255),.1)}.tab-badge{min-width:24px;height:16px;padding:0 6px;border-radius:10px;background:rgba(var(--header-content-color-rgb, 255, 255, 255),.2);color:var(--header-content-color, white);font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:600;display:flex;align-items:center;justify-content:center;line-height:1}.filter-tab.active .tab-badge{background:rgba(var(--color-on-header-accent-rgb, 255, 255, 255),.3);color:var(--color-on-header-accent, white)}.envelope-wrapper{position:relative;display:inline-flex;align-items:center;justify-content:center;margin-left:4px}.envelope-badge{position:absolute;top:-3px;right:-5px;background-color:#ff3b30;color:#fff;font-size:10px;font-weight:700;border-radius:9999px;min-width:12px;height:12px;display:flex;align-items:center;justify-content:center;padding:0;line-height:1;box-shadow:0 1px 2px #00000026;z-index:10}\n"] }]
6398
+ `, styles: [".filter-tabs{display:flex;align-items:center;gap:8px;flex-wrap:wrap;height:40px}.filter-tab{min-width:48px;justify-content:center;padding:0 12px;height:32px;border-radius:20px;background:transparent;border:none;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:rgba(var(--header-content-color-rgb, 255, 255, 255),.6);cursor:pointer;transition:all .2s ease;display:flex;align-items:center;gap:6px;white-space:nowrap}.filter-tab.active{background:var(--color-header-accent, #5d5fef);color:var(--color-on-header-accent, white)}.filter-tab:hover:not(.active){background:rgba(var(--header-content-color-rgb, 255, 255, 255),.1)}.filter-tab.active ds-mobile-count-badge{--header-content-color-rgb: var(--color-on-header-accent-rgb, 255, 255, 255);--header-content-color: var(--color-on-header-accent, white)}.filter-tab.has-envelope{gap:0}.envelope-wrapper{position:relative;display:inline-flex;align-items:center;justify-content:center;margin-left:4px}.envelope-badge{position:absolute;top:-3px;right:-5px;background-color:#ff3b30;color:#fff;font-size:10px;font-weight:700;border-radius:9999px;min-width:12px;height:12px;display:flex;align-items:center;justify-content:center;padding:0;line-height:1;box-shadow:0 1px 2px #00000026;z-index:10}\n"] }]
6334
6399
  }], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: true }] }], activeTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTab", required: true }] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }] } });
6335
6400
 
6336
6401
  /**
@@ -11754,7 +11819,7 @@ class DsMobileModalBaseComponent extends MobileModalBase {
11754
11819
  this.headerMeta() ||
11755
11820
  this.hasContentInSlot(this.headerLeading) ||
11756
11821
  this.hasContentInSlot(this.headerMain) ||
11757
- this.hasContentInSlot(this.headerTrailing));
11822
+ !!this.headerTrailing);
11758
11823
  }
11759
11824
  /**
11760
11825
  * Check whether header-leading slot has actual projected content.
@@ -11762,6 +11827,9 @@ class DsMobileModalBaseComponent extends MobileModalBase {
11762
11827
  hasHeaderLeadingContent() {
11763
11828
  return this.hasContentInSlot(this.headerLeading);
11764
11829
  }
11830
+ hasHeaderTrailingContent() {
11831
+ return !!this.headerTrailing;
11832
+ }
11765
11833
  /**
11766
11834
  * Check if a content child slot has actual content
11767
11835
  */
@@ -19244,11 +19312,14 @@ class DsMobileTabBarComponent {
19244
19312
  * ```
19245
19313
  */
19246
19314
  profileMenuItems;
19315
+ // Notification inputs
19316
+ notificationCount = 0;
19247
19317
  moreMenuItems = [];
19248
19318
  moreMenuLabel = 'Mere';
19249
19319
  moreMenuIcon = 'remixGridLine';
19250
19320
  moreMenuIconActive = 'remixGridFill';
19251
19321
  // Outputs
19322
+ notificationClick = new EventEmitter();
19252
19323
  avatarClick = new EventEmitter();
19253
19324
  /**
19254
19325
  * Emitted when a profile menu action is selected.
@@ -19834,7 +19905,7 @@ class DsMobileTabBarComponent {
19834
19905
  }
19835
19906
  }
19836
19907
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileTabBarComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
19837
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileTabBarComponent, isStandalone: true, selector: "ds-mobile-tab-bar", inputs: { tabs: "tabs", avatarType: "avatarType", avatarInitials: "avatarInitials", avatarSrc: "avatarSrc", avatarIconName: "avatarIconName", profileMenuItems: "profileMenuItems", moreMenuItems: "moreMenuItems", moreMenuLabel: "moreMenuLabel", moreMenuIcon: "moreMenuIcon", moreMenuIconActive: "moreMenuIconActive" }, outputs: { avatarClick: "avatarClick", profileActionSelected: "profileActionSelected", moreMenuItemSelected: "moreMenuItemSelected" }, ngImport: i0, template: `
19908
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileTabBarComponent, isStandalone: true, selector: "ds-mobile-tab-bar", inputs: { tabs: "tabs", avatarType: "avatarType", avatarInitials: "avatarInitials", avatarSrc: "avatarSrc", avatarIconName: "avatarIconName", profileMenuItems: "profileMenuItems", notificationCount: "notificationCount", moreMenuItems: "moreMenuItems", moreMenuLabel: "moreMenuLabel", moreMenuIcon: "moreMenuIcon", moreMenuIconActive: "moreMenuIconActive" }, outputs: { notificationClick: "notificationClick", avatarClick: "avatarClick", profileActionSelected: "profileActionSelected", moreMenuItemSelected: "moreMenuItemSelected" }, ngImport: i0, template: `
19838
19909
  <ion-tab-bar [attr.slot]="isDesktop() ? 'top' : 'bottom'" class="ds-tab-bar" [class.ds-tab-bar--desktop]="isDesktop()">
19839
19910
  <!-- Logo (desktop only, full logo in header) -->
19840
19911
  <div class="ds-tab-bar__logo">
@@ -19877,16 +19948,20 @@ class DsMobileTabBarComponent {
19877
19948
  }
19878
19949
  </div>
19879
19950
 
19880
- <!-- Avatar (desktop only, positioned via CSS) -->
19951
+ <!-- Notification + Avatar (desktop only, positioned via CSS) -->
19881
19952
  <div class="ds-tab-bar__actions">
19953
+ <ds-mobile-notification-button
19954
+ [count]="notificationCount"
19955
+ (clicked)="notificationClick.emit()"
19956
+ />
19882
19957
  <ds-avatar [size]="'md'" [type]="avatarType" [initials]="avatarInitials" [src]="avatarSrc" [iconName]="avatarIconName" (click)="handleAvatarClick()" />
19883
19958
  </div>
19884
19959
  </ion-tab-bar>
19885
- `, isInline: true, styles: [":host{--ds-tab-bar-height: 64px}@media (min-width: 768px){:host{display:block;--ds-tab-bar-height: 64px}}@media (max-width: 767px){:host{display:contents}}ion-tabs.ds-tabs-wrapper{height:100%;background:var(--color-header-surface)}ion-tab-button:before,ion-tab-button:after{content:none!important;display:none!important}ion-tab-button[title]:before,ion-tab-button[title]:after{display:none!important}ion-tab-button::part(native):before,ion-tab-button::part(native):after{display:none!important}.ds-tab-bar{--background: var(--color-background-neutral-primary);transition:transform .2s ease-in-out}ion-tab-bar[slot=bottom]{border-top:1px solid var(--border-color-default);padding-top:8px;padding-bottom:max(8px,calc(var(--app-safe-bottom, 0px) - 16px));padding-left:12px;padding-right:12px}@media (max-width: 767px){ion-tab-bar[slot=bottom]{position:fixed;bottom:0;left:0;right:0;z-index:100}}@media (max-width: 767px){:host-context(.plt-android) ion-tab-bar[slot=bottom]{padding-bottom:max(8px,var(--app-safe-bottom, 0px))!important}}@media (max-width: 767px){:host-context(ion-tabs:has(ds-mobile-page-details)) .ds-tab-bar{transform:translateY(100%);transition:transform .3s ease}}.ds-tab-bar__logo,.ds-tab-bar__actions{display:none}.ds-tab-bar__tabs{display:flex;width:100%;justify-content:space-around;align-items:center}@media (min-width: 769px){ion-tab-bar[slot=bottom]{padding-bottom:var(--app-safe-bottom, 0px)}}@media (display-mode: standalone){ion-tab-bar[slot=bottom]{padding-bottom:var(--app-safe-bottom, 0px)!important}}@media (min-width: 768px){:host[slot=top]{order:-1!important}:host ion-tab-bar{position:relative!important;bottom:auto!important;top:0!important}ion-tab-bar[slot=top]{--background: var(--color-header-surface);position:relative!important;display:flex!important;align-items:center;padding:12px 24px;height:64px;max-width:none;bottom:auto!important;top:0!important}ion-tab-bar[slot=bottom]{position:relative!important;bottom:auto!important}ion-tabs>div:not([slot]){order:1!important}.ds-tab-bar__logo{display:flex;position:absolute;left:24px;align-items:center;color:var(--header-content-color, white)}.ds-tab-bar__actions{display:flex;position:absolute;right:24px;align-items:center;gap:12px}.ds-tab-bar__tabs{display:flex;gap:8px;align-items:center;max-width:640px;width:100%;margin:0 auto;justify-content:center;padding-left:var(--content-padding-md);padding-right:var(--content-padding-md)}.logomark{height:28px;width:auto;flex-shrink:0}}@media (min-width: 992px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg);justify-content:center}}@media (min-width: 1440px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}ion-tab-button{--color: var(--text-color-default-tertiary);--color-selected: var(--color-accent);--padding-start: 0;--padding-end: 0;--ripple-color: transparent;display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;overflow:visible;pointer-events:auto}ion-tab-button[title]:before{content:attr(title);position:absolute;opacity:0;pointer-events:none}.tab-icon-ripple{position:absolute;left:50%;top:50%;width:40px;height:40px;border-radius:50%;background:var(--color-accent);transform:translate(-50%,-50%) scale(0);opacity:0;pointer-events:none;transition:all .6s cubic-bezier(.36,1.2,.04,1.4);z-index:0}.tab-selected .tab-icon-ripple{transform:translate(-50%,-50%) scale(2);opacity:.05;animation:ripple-fade .6s cubic-bezier(.36,.5,.04,1.8) forwards}@keyframes ripple-fade{0%{opacity:0;transform:translate(-50%,-50%) scale(0)}30%{opacity:.1}to{opacity:0;transform:translate(-50%,-50%) scale(2)}}ion-tab-button::part(native){overflow:visible}ion-tab-button ion-ripple-effect{color:var(--color-accent);border-radius:1000px}ion-tab-button ion-label{font-size:11px;font-weight:500;letter-spacing:-.5px;margin-top:0}.tab-icon-wrapper{position:relative;width:24px;height:24px;display:flex;align-items:center;justify-content:center;z-index:1;margin-bottom:4px}.tab-icon-inactive,.tab-icon-active{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);transition:all .8s cubic-bezier(.36,1,.04,1)}.tab-icon-inactive{opacity:1;transform:translate(-50%,-50%) scale(1)}.tab-icon-active{opacity:0;transform:translate(-50%,calc(-50% - 12px)) scale(.5)}.tab-selected .tab-icon-inactive{opacity:0;transform:translate(-50%,-50%) scale(.5)}.tab-selected .tab-icon-active{opacity:1;transform:translate(-50%,-50%) scale(1)}@media (min-width: 768px){.ds-tab-button{flex-direction:row;height:40px;padding:0 16px!important;border-radius:40px;transition:all .2s ease;width:-moz-fit-content;width:fit-content;min-width:-moz-fit-content;min-width:fit-content;flex:0 0 auto;--color: rgba(var(--color-header-content-rgb, 255, 255, 255), .7);--color-selected: var(--color-header-content, white);color:rgba(var(--color-header-content-rgb, 255, 255, 255),.7);background:transparent;position:relative;overflow:hidden}.tab-icon-wrapper,.tab-icon-ripple{width:20px;height:20px}.ds-tab-button::part(native){border-radius:40px}.ds-tab-button:hover:not(.tab-selected){--color: var(--color-header-content, white);--color-selected: var(--color-header-content, white);color:var(--color-header-content, white);background:rgba(var(--color-header-content-rgb, 255, 255, 255),.1)}.ds-tab-button:hover:not(.tab-selected) ion-label{color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon{--icon-color: var(--color-header-content, white);color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon svg{fill:var(--color-header-content, white)}.ds-tab-button.tab-selected,.ds-tab-button.tab-selected:hover{background:var(--color-header-accent);--color-selected: var(--color-on-header-accent, white);--color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover .tab-icon-active{opacity:1!important;visibility:visible!important}.ds-tab-button.tab-selected:hover .tab-icon-inactive{opacity:0!important}.ds-tab-button.tab-selected ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button .button-native{width:auto;padding:0}.ds-tab-button ion-label{font-size:var(--font-size-sm);font-weight:500;margin:0;color:inherit}.ds-tab-button .tab-icon-wrapper{margin-right:4px;margin-bottom:0}.ds-tab-button ion-ripple-effect{color:rgba(var(--header-content-color-rgb, 255, 255, 255),.3);border-radius:1000px;transform:scale(1.5)}}@media (min-width: 768px){.ds-tab-bar__actions ds-avatar{cursor:pointer;transition:transform .2s ease}.ds-tab-bar__actions ds-avatar:hover{transform:scale(1.05)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonTabBar, selector: "ion-tab-bar", inputs: ["color", "mode", "selectedTab", "translucent"] }, { kind: "component", type: IonTabButton, selector: "ion-tab-button", inputs: ["disabled", "download", "href", "layout", "mode", "rel", "selected", "tab", "target"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }] });
19960
+ `, isInline: true, styles: [":host{--ds-tab-bar-height: 64px}@media (min-width: 768px){:host{display:block;--ds-tab-bar-height: 64px}}@media (max-width: 767px){:host{display:contents}}ion-tabs.ds-tabs-wrapper{height:100%;background:var(--color-header-surface)}ion-tab-button:before,ion-tab-button:after{content:none!important;display:none!important}ion-tab-button[title]:before,ion-tab-button[title]:after{display:none!important}ion-tab-button::part(native):before,ion-tab-button::part(native):after{display:none!important}.ds-tab-bar{--background: var(--color-background-neutral-primary);transition:transform .2s ease-in-out}ion-tab-bar[slot=bottom]{border-top:1px solid var(--border-color-default);padding-top:8px;padding-bottom:max(8px,calc(var(--app-safe-bottom, 0px) - 16px));padding-left:12px;padding-right:12px}@media (max-width: 767px){ion-tab-bar[slot=bottom]{position:fixed;bottom:0;left:0;right:0;z-index:100}}@media (max-width: 767px){:host-context(.plt-android) ion-tab-bar[slot=bottom]{padding-bottom:max(8px,var(--app-safe-bottom, 0px))!important}}@media (max-width: 767px){:host-context(ion-tabs:has(ds-mobile-page-details)) .ds-tab-bar{transform:translateY(100%);transition:transform .3s ease}}.ds-tab-bar__logo,.ds-tab-bar__actions{display:none}.ds-tab-bar__tabs{display:flex;width:100%;justify-content:space-around;align-items:center}@media (min-width: 769px){ion-tab-bar[slot=bottom]{padding-bottom:var(--app-safe-bottom, 0px)}}@media (display-mode: standalone){ion-tab-bar[slot=bottom]{padding-bottom:var(--app-safe-bottom, 0px)!important}}@media (min-width: 768px){:host[slot=top]{order:-1!important}:host ion-tab-bar{position:relative!important;bottom:auto!important;top:0!important}ion-tab-bar[slot=top]{--background: var(--color-header-surface);position:relative!important;display:flex!important;align-items:center;padding:12px 24px;height:64px;max-width:none;bottom:auto!important;top:0!important}ion-tab-bar[slot=bottom]{position:relative!important;bottom:auto!important}ion-tabs>div:not([slot]){order:1!important}.ds-tab-bar__logo{display:flex;position:absolute;left:24px;align-items:center;color:var(--header-content-color, white)}.ds-tab-bar__actions{display:flex;position:absolute;right:24px;align-items:center;gap:12px}.ds-tab-bar__tabs{display:flex;gap:8px;align-items:center;max-width:640px;width:100%;margin:0 auto;justify-content:center;padding-left:var(--content-padding-md);padding-right:var(--content-padding-md)}.logomark{height:28px;width:auto;flex-shrink:0}}@media (min-width: 992px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg);justify-content:center}}@media (min-width: 1440px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}ion-tab-button{--color: var(--text-color-default-tertiary);--color-selected: var(--color-accent);--padding-start: 0;--padding-end: 0;--ripple-color: transparent;display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;overflow:visible;pointer-events:auto}ion-tab-button[title]:before{content:attr(title);position:absolute;opacity:0;pointer-events:none}.tab-icon-ripple{position:absolute;left:50%;top:50%;width:40px;height:40px;border-radius:50%;background:var(--color-accent);transform:translate(-50%,-50%) scale(0);opacity:0;pointer-events:none;transition:all .6s cubic-bezier(.36,1.2,.04,1.4);z-index:0}.tab-selected .tab-icon-ripple{transform:translate(-50%,-50%) scale(2);opacity:.05;animation:ripple-fade .6s cubic-bezier(.36,.5,.04,1.8) forwards}@keyframes ripple-fade{0%{opacity:0;transform:translate(-50%,-50%) scale(0)}30%{opacity:.1}to{opacity:0;transform:translate(-50%,-50%) scale(2)}}ion-tab-button::part(native){overflow:visible}ion-tab-button ion-ripple-effect{color:var(--color-accent);border-radius:1000px}ion-tab-button ion-label{font-size:11px;font-weight:500;letter-spacing:-.5px;margin-top:0}.tab-icon-wrapper{position:relative;width:24px;height:24px;display:flex;align-items:center;justify-content:center;z-index:1;margin-bottom:4px}.tab-icon-inactive,.tab-icon-active{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);transition:all .8s cubic-bezier(.36,1,.04,1)}.tab-icon-inactive{opacity:1;transform:translate(-50%,-50%) scale(1)}.tab-icon-active{opacity:0;transform:translate(-50%,calc(-50% - 12px)) scale(.5)}.tab-selected .tab-icon-inactive{opacity:0;transform:translate(-50%,-50%) scale(.5)}.tab-selected .tab-icon-active{opacity:1;transform:translate(-50%,-50%) scale(1)}@media (min-width: 768px){.ds-tab-button{flex-direction:row;height:40px;padding:0 16px!important;border-radius:40px;transition:all .2s ease;width:-moz-fit-content;width:fit-content;min-width:-moz-fit-content;min-width:fit-content;flex:0 0 auto;--color: rgba(var(--color-header-content-rgb, 255, 255, 255), .7);--color-selected: var(--color-header-content, white);color:rgba(var(--color-header-content-rgb, 255, 255, 255),.7);background:transparent;position:relative;overflow:hidden}.tab-icon-wrapper,.tab-icon-ripple{width:20px;height:20px}.ds-tab-button::part(native){border-radius:40px}.ds-tab-button:hover:not(.tab-selected){--color: var(--color-header-content, white);--color-selected: var(--color-header-content, white);color:var(--color-header-content, white);background:rgba(var(--color-header-content-rgb, 255, 255, 255),.1)}.ds-tab-button:hover:not(.tab-selected) ion-label{color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon{--icon-color: var(--color-header-content, white);color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon svg{fill:var(--color-header-content, white)}.ds-tab-button.tab-selected,.ds-tab-button.tab-selected:hover{background:var(--color-header-accent);--color-selected: var(--color-on-header-accent, white);--color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover .tab-icon-active{opacity:1!important;visibility:visible!important}.ds-tab-button.tab-selected:hover .tab-icon-inactive{opacity:0!important}.ds-tab-button.tab-selected ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button .button-native{width:auto;padding:0}.ds-tab-button ion-label{font-size:var(--font-size-sm);font-weight:500;margin:0;color:inherit}.ds-tab-button .tab-icon-wrapper{margin-right:4px;margin-bottom:0}.ds-tab-button ion-ripple-effect{color:rgba(var(--header-content-color-rgb, 255, 255, 255),.3);border-radius:1000px;transform:scale(1.5)}}@media (min-width: 768px){.ds-tab-bar__actions ds-avatar{cursor:pointer;transition:transform .2s ease}.ds-tab-bar__actions ds-avatar:hover{transform:scale(1.05)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonTabBar, selector: "ion-tab-bar", inputs: ["color", "mode", "selectedTab", "translucent"] }, { kind: "component", type: IonTabButton, selector: "ion-tab-button", inputs: ["disabled", "download", "href", "layout", "mode", "rel", "selected", "tab", "target"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }, { kind: "component", type: DsMobileNotificationButtonComponent, selector: "ds-mobile-notification-button", inputs: ["count"], outputs: ["clicked"] }] });
19886
19961
  }
19887
19962
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileTabBarComponent, decorators: [{
19888
19963
  type: Component,
19889
- args: [{ selector: 'ds-mobile-tab-bar', standalone: true, imports: [CommonModule, IonTabBar, IonTabButton, IonLabel, DsIconComponent, DsAvatarComponent, DsLogoComponent], template: `
19964
+ args: [{ selector: 'ds-mobile-tab-bar', standalone: true, imports: [CommonModule, IonTabBar, IonTabButton, IonLabel, DsIconComponent, DsAvatarComponent, DsLogoComponent, DsMobileNotificationButtonComponent], template: `
19890
19965
  <ion-tab-bar [attr.slot]="isDesktop() ? 'top' : 'bottom'" class="ds-tab-bar" [class.ds-tab-bar--desktop]="isDesktop()">
19891
19966
  <!-- Logo (desktop only, full logo in header) -->
19892
19967
  <div class="ds-tab-bar__logo">
@@ -19929,8 +20004,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
19929
20004
  }
19930
20005
  </div>
19931
20006
 
19932
- <!-- Avatar (desktop only, positioned via CSS) -->
20007
+ <!-- Notification + Avatar (desktop only, positioned via CSS) -->
19933
20008
  <div class="ds-tab-bar__actions">
20009
+ <ds-mobile-notification-button
20010
+ [count]="notificationCount"
20011
+ (clicked)="notificationClick.emit()"
20012
+ />
19934
20013
  <ds-avatar [size]="'md'" [type]="avatarType" [initials]="avatarInitials" [src]="avatarSrc" [iconName]="avatarIconName" (click)="handleAvatarClick()" />
19935
20014
  </div>
19936
20015
  </ion-tab-bar>
@@ -19947,6 +20026,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
19947
20026
  type: Input
19948
20027
  }], profileMenuItems: [{
19949
20028
  type: Input
20029
+ }], notificationCount: [{
20030
+ type: Input
19950
20031
  }], moreMenuItems: [{
19951
20032
  type: Input
19952
20033
  }], moreMenuLabel: [{
@@ -19955,6 +20036,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
19955
20036
  type: Input
19956
20037
  }], moreMenuIconActive: [{
19957
20038
  type: Input
20039
+ }], notificationClick: [{
20040
+ type: Output
19958
20041
  }], avatarClick: [{
19959
20042
  type: Output
19960
20043
  }], profileActionSelected: [{
@@ -20023,7 +20106,7 @@ class DsMobileTabsComponent {
20023
20106
  (avatarClick)="handleAvatarClick()"
20024
20107
  />
20025
20108
  </ion-tabs>
20026
- `, isInline: true, styles: [":host{display:block;height:100vh;height:100dvh}ion-tabs{height:100%;background:var(--color-header-surface)}@media (max-width: 767px){ion-tabs:has(ds-mobile-page-details) ds-mobile-tab-bar{--tab-bar-transform: translateY(100%)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonTabs, selector: "ion-tabs" }, { kind: "component", type: IonTab, selector: "ion-tab", inputs: ["component", "tab"] }, { kind: "component", type: DsMobileTabBarComponent, selector: "ds-mobile-tab-bar", inputs: ["tabs", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "profileMenuItems", "moreMenuItems", "moreMenuLabel", "moreMenuIcon", "moreMenuIconActive"], outputs: ["avatarClick", "profileActionSelected", "moreMenuItemSelected"] }] });
20109
+ `, isInline: true, styles: [":host{display:block;height:100vh;height:100dvh}ion-tabs{height:100%;background:var(--color-header-surface)}@media (max-width: 767px){ion-tabs:has(ds-mobile-page-details) ds-mobile-tab-bar{--tab-bar-transform: translateY(100%)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonTabs, selector: "ion-tabs" }, { kind: "component", type: IonTab, selector: "ion-tab", inputs: ["component", "tab"] }, { kind: "component", type: DsMobileTabBarComponent, selector: "ds-mobile-tab-bar", inputs: ["tabs", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "profileMenuItems", "notificationCount", "moreMenuItems", "moreMenuLabel", "moreMenuIcon", "moreMenuIconActive"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "moreMenuItemSelected"] }] });
20027
20110
  }
20028
20111
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileTabsComponent, decorators: [{
20029
20112
  type: Component,
@@ -21804,7 +21887,7 @@ class DsMobileEmptyStateComponent {
21804
21887
  }
21805
21888
  </div>
21806
21889
  </div>
21807
- `, isInline: true, styles: [":host{display:block}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;gap:24px}.empty-state-image{width:96px;height:96px;margin:0 auto}.empty-state-text{display:flex;flex-direction:column;gap:8px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:1.3;color:var(--color-text-primary, #1a1a1a);margin:0}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--color-text-secondary, #737373);margin:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
21890
+ `, isInline: true, styles: [":host{display:block}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;gap:24px}.empty-state-image{width:120px;height:120px;margin:0 auto}.empty-state-text{display:flex;flex-direction:column;gap:8px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:1.3;color:var(--color-text-primary, #1a1a1a);margin:0}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--color-text-secondary, #737373);margin:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
21808
21891
  }
21809
21892
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileEmptyStateComponent, decorators: [{
21810
21893
  type: Component,
@@ -21823,7 +21906,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
21823
21906
  }
21824
21907
  </div>
21825
21908
  </div>
21826
- `, styles: [":host{display:block}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;gap:24px}.empty-state-image{width:96px;height:96px;margin:0 auto}.empty-state-text{display:flex;flex-direction:column;gap:8px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:1.3;color:var(--color-text-primary, #1a1a1a);margin:0}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--color-text-secondary, #737373);margin:0}\n"] }]
21909
+ `, styles: [":host{display:block}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;gap:24px}.empty-state-image{width:120px;height:120px;margin:0 auto}.empty-state-text{display:flex;flex-direction:column;gap:8px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:1.3;color:var(--color-text-primary, #1a1a1a);margin:0}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--color-text-secondary, #737373);margin:0}\n"] }]
21827
21910
  }], propDecorators: { imageSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageSrc", required: false }] }], imageAlt: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageAlt", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }] } });
21828
21911
 
21829
21912
  /**
@@ -22700,16 +22783,16 @@ class DsMobileCardInlineBannerComponent {
22700
22783
 
22701
22784
  <div content-trailing class="item-trailing">
22702
22785
  @if (unreadCount() && unreadCount()! > 0) {
22703
- <span class="unread-badge">{{ unreadCount() }}</span>
22786
+ <ds-mobile-count-badge [count]="unreadCount()!" variant="accent" />
22704
22787
  }
22705
22788
  <ds-icon name="remixArrowRightSLine" size="20px" />
22706
22789
  </div>
22707
22790
  </ds-mobile-card-inline>
22708
- `, isInline: true, styles: [".unread-badge{min-width:24px;height:16px;padding:0 6px;border-radius:10px;background:var(--color-accent, #6B5FF5);color:var(--color-on-accent, #ffffff);font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:600;display:flex;align-items:center;justify-content:center;line-height:1;margin-right:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsMobileCardInlineComponent, selector: "ds-mobile-card-inline", inputs: ["variant", "disabled"], outputs: ["cardClick"] }] });
22791
+ `, isInline: true, styles: ["ds-mobile-count-badge{margin-right:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsMobileCardInlineComponent, selector: "ds-mobile-card-inline", inputs: ["variant", "disabled"], outputs: ["cardClick"] }, { kind: "component", type: DsMobileCountBadgeComponent, selector: "ds-mobile-count-badge", inputs: ["count", "variant"] }] });
22709
22792
  }
22710
22793
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileCardInlineBannerComponent, decorators: [{
22711
22794
  type: Component,
22712
- args: [{ selector: 'ds-mobile-card-inline-banner', standalone: true, imports: [CommonModule, DsIconComponent, DsAvatarComponent, DsMobileCardInlineComponent], template: `
22795
+ args: [{ selector: 'ds-mobile-card-inline-banner', standalone: true, imports: [CommonModule, DsIconComponent, DsAvatarComponent, DsMobileCardInlineComponent, DsMobileCountBadgeComponent], template: `
22713
22796
  <ds-mobile-card-inline
22714
22797
  [variant]="layout()"
22715
22798
  (cardClick)="handleBannerClick()">
@@ -22734,12 +22817,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
22734
22817
 
22735
22818
  <div content-trailing class="item-trailing">
22736
22819
  @if (unreadCount() && unreadCount()! > 0) {
22737
- <span class="unread-badge">{{ unreadCount() }}</span>
22820
+ <ds-mobile-count-badge [count]="unreadCount()!" variant="accent" />
22738
22821
  }
22739
22822
  <ds-icon name="remixArrowRightSLine" size="20px" />
22740
22823
  </div>
22741
22824
  </ds-mobile-card-inline>
22742
- `, styles: [".unread-badge{min-width:24px;height:16px;padding:0 6px;border-radius:10px;background:var(--color-accent, #6B5FF5);color:var(--color-on-accent, #ffffff);font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:600;display:flex;align-items:center;justify-content:center;line-height:1;margin-right:8px}\n"] }]
22825
+ `, styles: ["ds-mobile-count-badge{margin-right:8px}\n"] }]
22743
22826
  }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], timestamp: [{ type: i0.Input, args: [{ isSignal: true, alias: "timestamp", required: false }] }], unreadCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "unreadCount", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], bannerClick: [{ type: i0.Output, args: ["bannerClick"] }] } });
22744
22827
 
22745
22828
  /**
@@ -26090,7 +26173,7 @@ class DsMobilePriceSheetComponent {
26090
26173
  inputmode="numeric"
26091
26174
  pattern="[0-9]*"
26092
26175
  maxlength="5"
26093
- [attr.size]="Math.min(customPrice()?.length || 2, 5)"
26176
+ [attr.size]="Math.min(customPrice().length || 2, 5)"
26094
26177
  [(ngModel)]="customPrice"
26095
26178
  placeholder="75"
26096
26179
  class="price-input-native"
@@ -26145,7 +26228,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
26145
26228
  inputmode="numeric"
26146
26229
  pattern="[0-9]*"
26147
26230
  maxlength="5"
26148
- [attr.size]="Math.min(customPrice()?.length || 2, 5)"
26231
+ [attr.size]="Math.min(customPrice().length || 2, 5)"
26149
26232
  [(ngModel)]="customPrice"
26150
26233
  placeholder="75"
26151
26234
  class="price-input-native"
@@ -28622,7 +28705,713 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
28622
28705
  `, styles: [":host{display:block;width:100%}.property-banner{display:flex;align-items:center;gap:8px;height:44px;padding:4px 10px 4px 6px;background:rgba(var(--header-content-color-rgb, 255, 255, 255),.1);border:none;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-radius:12px;cursor:pointer;transition:background .2s ease;-webkit-tap-highlight-color:transparent}.property-banner:hover{background:rgba(var(--header-content-color-rgb, 255, 255, 255),.12)}.property-banner:active{background:rgba(var(--header-content-color-rgb, 255, 255, 255),.15)}.property-photo{width:32px;height:32px;border-radius:8px;object-fit:cover;flex-shrink:0;background:rgba(var(--header-content-color-rgb, 255, 255, 255),.1)}.property-text{display:flex;flex-direction:column;flex:1;min-width:0}.property-address{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:var(--header-content-color, white);line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.property-meta{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:rgba(var(--header-content-color-rgb, 255, 255, 255),.65);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.property-chevron{flex-shrink:0;width:16px;height:16px;color:rgba(var(--header-content-color-rgb, 255, 255, 255),.5)}\n"] }]
28623
28706
  }], propDecorators: { address: [{ type: i0.Input, args: [{ isSignal: true, alias: "address", required: true }] }], photoUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "photoUrl", required: true }] }], tenantCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "tenantCount", required: false }] }] } });
28624
28707
 
28625
- // Mobile Page Components
28708
+ class DsMobileNotificationPromptComponent {
28709
+ whitelabel = inject(WhitelabelService);
28710
+ dismissing = signal(false, ...(ngDevMode ? [{ debugName: "dismissing" }] : []));
28711
+ heading = input('Vær den første til at vide det', ...(ngDevMode ? [{ debugName: "heading" }] : []));
28712
+ subtitle = input('Hold dig opdateret med henvendelser, bookinger, nyheder og alt derimellem.', ...(ngDevMode ? [{ debugName: "subtitle" }] : []));
28713
+ allowLabel = input('Slå notifikationer til', ...(ngDevMode ? [{ debugName: "allowLabel" }] : []));
28714
+ dismissLabel = input('Ikke lige nu', ...(ngDevMode ? [{ debugName: "dismissLabel" }] : []));
28715
+ allow = output();
28716
+ dismiss = output();
28717
+ static EXIT_DURATION = 800;
28718
+ handleAllow() {
28719
+ if (this.dismissing())
28720
+ return;
28721
+ this.dismissing.set(true);
28722
+ setTimeout(() => this.allow.emit(), DsMobileNotificationPromptComponent.EXIT_DURATION);
28723
+ }
28724
+ handleDismiss() {
28725
+ if (this.dismissing())
28726
+ return;
28727
+ this.dismissing.set(true);
28728
+ setTimeout(() => this.dismiss.emit(), DsMobileNotificationPromptComponent.EXIT_DURATION);
28729
+ }
28730
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationPromptComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
28731
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: DsMobileNotificationPromptComponent, isStandalone: true, selector: "ds-mobile-notification-prompt", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, allowLabel: { classPropertyName: "allowLabel", publicName: "allowLabel", isSignal: true, isRequired: false, transformFunction: null }, dismissLabel: { classPropertyName: "dismissLabel", publicName: "dismissLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { allow: "allow", dismiss: "dismiss" }, ngImport: i0, template: `
28732
+ <div class="notification-prompt" [class.is-dismissing]="dismissing()">
28733
+ <div
28734
+ class="notification-prompt__backdrop"
28735
+ [style.background]="whitelabel.headerSurface()">
28736
+ </div>
28737
+
28738
+ <!-- Illustration: floating icon tiles -->
28739
+ <div class="notification-prompt__illustration">
28740
+ <div class="icon-tiles">
28741
+
28742
+ <!-- Tile 1: service/heart-handshake (top-center, small 72px) -->
28743
+ <div class="tile-entry tile-entry--1">
28744
+ <div class="icon-tile icon-tile--1">
28745
+ <div class="icon-tile__icon">
28746
+ <svg viewBox="0 0 45 45" fill="none">
28747
+ <path d="M5.928 8.379c4.545-4.545 11.787-4.75 16.575-.615 4.785-4.135 12.026-3.93 16.571.615 4.537 4.537 4.749 11.76.637 16.549L25.153 39.536a3.75 3.75 0 0 1-5.098.19l-.206-.19L5.291 24.928c-4.112-4.789-3.9-12.012.637-16.549zm2.652 2.651c-3.2 3.201-3.292 8.334-.274 11.645l.274.288L22.5 36.884l9.943-9.945-6.628-6.628-1.988 1.989a4.688 4.688 0 0 1-6.628-6.628l3.94-3.943c-3.212-2.57-7.866-2.453-10.945.245l-.287.275zm15.91 5.303a1.875 1.875 0 0 1 2.651 0l7.954 7.954 1.327-1.325c3.295-3.295 3.295-8.637 0-11.932-3.2-3.201-8.333-3.292-11.645-.274l-.287.274-5.967 5.967a1.875 1.875 0 0 0-.146 2.497l.146.164a1.875 1.875 0 0 0 2.497.145l.164-.145 3.314-3.315z" fill="currentColor"/>
28748
+ </svg>
28749
+ </div>
28750
+ </div>
28751
+ </div>
28752
+
28753
+ <!-- Tile 2: calendar (right-bottom, medium 88px) -->
28754
+ <div class="tile-entry tile-entry--2">
28755
+ <div class="icon-tile icon-tile--medium icon-tile--2">
28756
+ <div class="icon-tile__icon">
28757
+ <svg viewBox="0 0 24 24" fill="none">
28758
+ <path d="M9 1v2h6V1h2v2h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h4V1h2zm11 9H4v9h16v-9zm-4.964 1.136l1.414 1.414-4.95 4.95-3.536-3.536L9.38 12.55l2.121 2.122 3.536-3.536zM7 5H4v3h16V5h-3v1h-2V5H9v1H7V5z" fill="currentColor"/>
28759
+ </svg>
28760
+ </div>
28761
+ </div>
28762
+ </div>
28763
+
28764
+ <!-- Tile 3: documents (bottom-left, small 72px) -->
28765
+ <div class="tile-entry tile-entry--3">
28766
+ <div class="icon-tile icon-tile--3">
28767
+ <div class="icon-tile__icon">
28768
+ <svg viewBox="0 0 24 24" fill="none">
28769
+ <path d="M20 22H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1zM19 4H5v16h14V4zM7 6h4v4H7V6zm0 6h10v2H7v-2zm0 4h10v2H7v-2zm6-9h4v2h-4V7z" fill="currentColor"/>
28770
+ </svg>
28771
+ </div>
28772
+ </div>
28773
+ </div>
28774
+
28775
+ <!-- Tile 4: messages (right-upper, medium 88px) -->
28776
+ <div class="tile-entry tile-entry--4">
28777
+ <div class="icon-tile icon-tile--medium icon-tile--4">
28778
+ <div class="icon-tile__icon">
28779
+ <svg viewBox="0 0 24 24" fill="none">
28780
+ <path d="M7.291 20.824L2 22l1.176-5.291A9.956 9.956 0 0 1 2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10a9.956 9.956 0 0 1-4.709-1.176zM7.581 18.71l.29.158A7.956 7.956 0 0 0 12 20c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8c0 1.458.39 2.823 1.131 4.13l.159.288-.69 3.1 3.1-.69z" fill="currentColor"/>
28781
+ </svg>
28782
+ </div>
28783
+ </div>
28784
+ </div>
28785
+
28786
+ <!-- Tile 5: home (left-middle, medium 88px) -->
28787
+ <div class="tile-entry tile-entry--5">
28788
+ <div class="icon-tile icon-tile--medium icon-tile--5">
28789
+ <div class="icon-tile__icon">
28790
+ <svg viewBox="0 0 24 24" fill="none">
28791
+ <path d="M19 21H5a1 1 0 0 1-1-1v-9H1l10.327-9.388a1 1 0 0 1 1.346 0L23 11h-3v9a1 1 0 0 1-1 1zM6 19h12v-9.157l-6-5.454-6 5.454V19zm2-4h8v2H8v-2z" fill="currentColor"/>
28792
+ </svg>
28793
+ </div>
28794
+ </div>
28795
+ </div>
28796
+
28797
+ <!-- Hero: bell icon (center, 120px) -->
28798
+ <div class="tile-entry tile-entry--hero">
28799
+ <div class="icon-tile icon-tile--hero">
28800
+ <div class="icon-tile__icon">
28801
+ <svg viewBox="0 0 24 24" fill="none">
28802
+ <path d="M20 17h2v2H2v-2h2v-7a8 8 0 1 1 16 0v7zm-2 0v-7a6 6 0 1 0-12 0v7h12zm-9 4h6v2H9v-2z" fill="currentColor"/>
28803
+ </svg>
28804
+ </div>
28805
+ <span class="icon-tile__badge">18</span>
28806
+ </div>
28807
+ </div>
28808
+
28809
+ </div>
28810
+ </div>
28811
+
28812
+ <!-- Heading + subtitle -->
28813
+ <div class="notification-prompt__texts">
28814
+ <h1 class="notification-prompt__heading">{{ heading() }}</h1>
28815
+ <p class="notification-prompt__subtitle">{{ subtitle() }}</p>
28816
+ </div>
28817
+
28818
+ <!-- CTA buttons -->
28819
+ <div class="notification-prompt__actions">
28820
+ <ds-button
28821
+ variant="primary"
28822
+ size="md"
28823
+ (clicked)="handleAllow()">
28824
+ {{ allowLabel() }}
28825
+ </ds-button>
28826
+ <ds-button
28827
+ class="dismiss-btn"
28828
+ variant="ghost"
28829
+ size="md"
28830
+ (clicked)="handleDismiss()">
28831
+ {{ dismissLabel() }}
28832
+ </ds-button>
28833
+ </div>
28834
+ </div>
28835
+ `, isInline: true, styles: ["@keyframes fadeSlideUp{0%{opacity:0;transform:translateY(32px)}to{opacity:1;transform:translateY(0)}}@keyframes tileEntry{0%{opacity:0;transform:translateY(40px) scale(.85)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes float1{0%{transform:var(--tile-transform) translate(0) rotate(0)}11%{transform:var(--tile-transform) translate(1.4px,-3px) rotate(.9deg)}23%{transform:var(--tile-transform) translate(2.2px,-6.5px) rotate(1.8deg)}37%{transform:var(--tile-transform) translate(.6px,-8.5px) rotate(.4deg)}50%{transform:var(--tile-transform) translate(-1.2px,-7px) rotate(-.8deg)}63%{transform:var(--tile-transform) translate(-1.8px,-4px) rotate(-1.6deg)}76%{transform:var(--tile-transform) translate(-.4px,-1.5px) rotate(-.5deg)}88%{transform:var(--tile-transform) translate(.8px,-.5px) rotate(.3deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float2{0%{transform:var(--tile-transform) translate(0) rotate(0)}12%{transform:var(--tile-transform) translate(-1px,-2.5px) rotate(-.7deg)}25%{transform:var(--tile-transform) translate(-2px,-5.5px) rotate(-1.5deg)}38%{transform:var(--tile-transform) translate(-.8px,-7px) rotate(-.3deg)}50%{transform:var(--tile-transform) translate(1px,-6px) rotate(1deg)}62%{transform:var(--tile-transform) translate(1.6px,-3.5px) rotate(1.6deg)}75%{transform:var(--tile-transform) translate(.5px,-1.5px) rotate(.6deg)}87%{transform:var(--tile-transform) translate(-.5px,-.3px) rotate(-.2deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float3{0%{transform:var(--tile-transform) translate(0) rotate(0)}10%{transform:var(--tile-transform) translate(.8px,-3px) rotate(1.2deg)}22%{transform:var(--tile-transform) translate(1.5px,-7px) rotate(2deg)}35%{transform:var(--tile-transform) translate(.3px,-10px) rotate(.8deg)}48%{transform:var(--tile-transform) translate(-1.5px,-9.5px) rotate(-.6deg)}60%{transform:var(--tile-transform) translate(-2px,-6px) rotate(-1.5deg)}72%{transform:var(--tile-transform) translate(-1px,-3px) rotate(-.8deg)}85%{transform:var(--tile-transform) translate(.3px,-1px) rotate(.3deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float4{0%{transform:var(--tile-transform) translate(0) rotate(0)}13%{transform:var(--tile-transform) translate(-.8px,-2px) rotate(-.8deg)}26%{transform:var(--tile-transform) translate(-1.5px,-5px) rotate(-1.6deg)}40%{transform:var(--tile-transform) translate(.2px,-7.5px) rotate(-.2deg)}52%{transform:var(--tile-transform) translate(1.8px,-6.5px) rotate(1.2deg)}65%{transform:var(--tile-transform) translate(1.2px,-4px) rotate(.8deg)}78%{transform:var(--tile-transform) translate(-.3px,-1.8px) rotate(-.4deg)}90%{transform:var(--tile-transform) translate(-.6px,-.4px) rotate(-.1deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float5{0%{transform:var(--tile-transform) translate(0) rotate(0)}9%{transform:var(--tile-transform) translate(1.2px,-2px) rotate(.6deg)}20%{transform:var(--tile-transform) translate(2px,-5px) rotate(1.5deg)}33%{transform:var(--tile-transform) translate(.8px,-8.5px) rotate(.5deg)}45%{transform:var(--tile-transform) translate(-1px,-9.5px) rotate(-1deg)}58%{transform:var(--tile-transform) translate(-1.8px,-7px) rotate(-1.8deg)}70%{transform:var(--tile-transform) translate(-.8px,-4px) rotate(-.8deg)}82%{transform:var(--tile-transform) translate(.4px,-1.5px) rotate(.2deg)}92%{transform:var(--tile-transform) translate(.6px,-.3px) rotate(.1deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes floatHero{0%{transform:translate(-50%) translate(0) rotate(0) scale(1)}10%{transform:translate(-50%) translate(.8px,-4px) rotate(.7deg) scale(1.006)}22%{transform:translate(-50%) translate(1.5px,-9px) rotate(1.4deg) scale(1.014)}35%{transform:translate(-50%) translate(.2px,-13px) rotate(.3deg) scale(1.02)}48%{transform:translate(-50%) translate(-1.5px,-12px) rotate(-1deg) scale(1.016)}60%{transform:translate(-50%) translate(-2px,-8px) rotate(-1.6deg) scale(1.01)}72%{transform:translate(-50%) translate(-.6px,-4px) rotate(-.5deg) scale(1.005)}85%{transform:translate(-50%) translate(.5px,-1.5px) rotate(.3deg) scale(1.002)}94%{transform:translate(-50%) translate(.3px,-.3px) rotate(.1deg) scale(1.001)}to{transform:translate(-50%) translate(0) rotate(0) scale(1)}}:host{display:flex;align-items:center;justify-content:center;position:fixed;inset:0;z-index:9999}.notification-prompt{width:100%;max-width:390px;height:100%;display:flex;flex-direction:column;gap:48px;overflow:clip;position:relative}.notification-prompt__backdrop{position:fixed;inset:0;z-index:-1}.notification-prompt__illustration{position:relative;flex:1 0 0;min-height:0;display:flex;align-items:center;justify-content:center;padding:0 24px}.icon-tiles{position:relative;width:100%;max-width:342px;height:100%}.tile-entry{position:absolute;opacity:0;animation:tileEntry .7s cubic-bezier(.22,1,.36,1) forwards;will-change:transform,opacity}.tile-entry--hero{inset:0;animation-delay:.2s;animation-duration:.8s}.tile-entry--1{inset:0;animation-delay:.35s}.tile-entry--4{inset:0;animation-delay:.3s}.tile-entry--5{inset:0;animation-delay:.4s}.tile-entry--3{inset:0;animation-delay:.45s}.tile-entry--2{inset:0;animation-delay:.5s}.icon-tile{position:absolute;display:flex;align-items:center;justify-content:center;border-radius:20%;background:linear-gradient(180deg,#0000 95%,#00000026),linear-gradient(180deg,#fff6,#fff0 4.5%),linear-gradient(180deg,#ffffff26,#fff0),linear-gradient(180deg,#0000,#0000001a),var(--color-accent, #6B5FF5);will-change:transform}.icon-tile__icon{width:62.5%;height:62.5%;display:flex;align-items:center;justify-content:center}.icon-tile__icon svg{width:100%;height:100%;color:#fff;display:block}.icon-tile--1{--tile-transform: translate(-50%, 0) rotate(7.74deg);width:72px;height:72px;top:22%;left:50%;filter:blur(2px);animation:float1 6.8s linear infinite}.icon-tile--2{--tile-transform: rotate(9.27deg);width:88px;height:88px;bottom:6%;right:0;filter:blur(1px);animation:float2 7.4s linear infinite}.icon-tile--3{--tile-transform: rotate(-6.18deg);width:72px;height:72px;bottom:0;left:0;filter:blur(2px);animation:float3 8.6s linear infinite}.icon-tile--4{--tile-transform: rotate(20.57deg);width:88px;height:88px;top:36%;right:0;filter:blur(1px);animation:float4 7.1s linear infinite}.icon-tile--5{--tile-transform: rotate(-8.24deg);width:88px;height:88px;top:44%;left:0;filter:blur(1px);animation:float5 9.2s linear infinite}.icon-tile--hero{width:120px;height:120px;left:50%;bottom:14%;filter:none;border-radius:30px;z-index:2;animation:floatHero 6.2s linear infinite}.icon-tile__badge{position:absolute;top:12.5%;right:12%;min-width:26px;height:26px;padding:0 7px;border-radius:8px;background:var(--content-feedback-bold-red, #b91c1c);display:flex;align-items:center;justify-content:center;font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:18px;line-height:1;letter-spacing:-.04em;color:#fff;white-space:nowrap;box-sizing:border-box}.notification-prompt__texts{position:relative;z-index:1;text-align:center;padding:0 16px;flex-shrink:0;opacity:0;animation:fadeSlideUp .6s .6s cubic-bezier(.22,1,.36,1) forwards}.notification-prompt__heading{font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:32px;line-height:1.15;letter-spacing:-.02em;color:#fff;margin:0 0 4px}.notification-prompt__subtitle{font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:400;font-size:18px;line-height:1.4;letter-spacing:-.02em;color:#eef0ff;margin:0}.notification-prompt__actions{position:relative;z-index:1;display:flex;flex-direction:column;gap:8px;padding:0 24px 24px;padding-bottom:max(24px,env(safe-area-inset-bottom,24px));flex-shrink:0;opacity:0;animation:fadeSlideUp .6s .75s cubic-bezier(.22,1,.36,1) forwards}.notification-prompt__actions ds-button{display:block;width:100%}.notification-prompt__actions ds-button::ng-deep button{width:100%;border-radius:8px}.notification-prompt__actions ds-button.dismiss-btn::ng-deep button{color:#fff;background:transparent}@keyframes exitUp{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-48px)}}@keyframes exitFade{0%{opacity:1}to{opacity:0}}.is-dismissing .notification-prompt__illustration{animation:exitUp .5s cubic-bezier(.55,0,1,.45) forwards}.is-dismissing .notification-prompt__texts{animation:exitUp .45s .15s cubic-bezier(.55,0,1,.45) forwards}.is-dismissing .notification-prompt__actions{animation:exitUp .45s .3s cubic-bezier(.55,0,1,.45) forwards}.is-dismissing .notification-prompt__backdrop{animation:exitFade .35s .45s cubic-bezier(.55,0,1,.45) forwards}@media (prefers-reduced-motion: reduce){.tile-entry,.icon-tile,.icon-tile--hero,.notification-prompt__texts,.notification-prompt__actions{animation:none!important;opacity:1!important}}\n"], dependencies: [{ kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }] });
28836
+ }
28837
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationPromptComponent, decorators: [{
28838
+ type: Component,
28839
+ args: [{ selector: 'ds-mobile-notification-prompt', standalone: true, imports: [DsButtonComponent], template: `
28840
+ <div class="notification-prompt" [class.is-dismissing]="dismissing()">
28841
+ <div
28842
+ class="notification-prompt__backdrop"
28843
+ [style.background]="whitelabel.headerSurface()">
28844
+ </div>
28845
+
28846
+ <!-- Illustration: floating icon tiles -->
28847
+ <div class="notification-prompt__illustration">
28848
+ <div class="icon-tiles">
28849
+
28850
+ <!-- Tile 1: service/heart-handshake (top-center, small 72px) -->
28851
+ <div class="tile-entry tile-entry--1">
28852
+ <div class="icon-tile icon-tile--1">
28853
+ <div class="icon-tile__icon">
28854
+ <svg viewBox="0 0 45 45" fill="none">
28855
+ <path d="M5.928 8.379c4.545-4.545 11.787-4.75 16.575-.615 4.785-4.135 12.026-3.93 16.571.615 4.537 4.537 4.749 11.76.637 16.549L25.153 39.536a3.75 3.75 0 0 1-5.098.19l-.206-.19L5.291 24.928c-4.112-4.789-3.9-12.012.637-16.549zm2.652 2.651c-3.2 3.201-3.292 8.334-.274 11.645l.274.288L22.5 36.884l9.943-9.945-6.628-6.628-1.988 1.989a4.688 4.688 0 0 1-6.628-6.628l3.94-3.943c-3.212-2.57-7.866-2.453-10.945.245l-.287.275zm15.91 5.303a1.875 1.875 0 0 1 2.651 0l7.954 7.954 1.327-1.325c3.295-3.295 3.295-8.637 0-11.932-3.2-3.201-8.333-3.292-11.645-.274l-.287.274-5.967 5.967a1.875 1.875 0 0 0-.146 2.497l.146.164a1.875 1.875 0 0 0 2.497.145l.164-.145 3.314-3.315z" fill="currentColor"/>
28856
+ </svg>
28857
+ </div>
28858
+ </div>
28859
+ </div>
28860
+
28861
+ <!-- Tile 2: calendar (right-bottom, medium 88px) -->
28862
+ <div class="tile-entry tile-entry--2">
28863
+ <div class="icon-tile icon-tile--medium icon-tile--2">
28864
+ <div class="icon-tile__icon">
28865
+ <svg viewBox="0 0 24 24" fill="none">
28866
+ <path d="M9 1v2h6V1h2v2h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h4V1h2zm11 9H4v9h16v-9zm-4.964 1.136l1.414 1.414-4.95 4.95-3.536-3.536L9.38 12.55l2.121 2.122 3.536-3.536zM7 5H4v3h16V5h-3v1h-2V5H9v1H7V5z" fill="currentColor"/>
28867
+ </svg>
28868
+ </div>
28869
+ </div>
28870
+ </div>
28871
+
28872
+ <!-- Tile 3: documents (bottom-left, small 72px) -->
28873
+ <div class="tile-entry tile-entry--3">
28874
+ <div class="icon-tile icon-tile--3">
28875
+ <div class="icon-tile__icon">
28876
+ <svg viewBox="0 0 24 24" fill="none">
28877
+ <path d="M20 22H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1zM19 4H5v16h14V4zM7 6h4v4H7V6zm0 6h10v2H7v-2zm0 4h10v2H7v-2zm6-9h4v2h-4V7z" fill="currentColor"/>
28878
+ </svg>
28879
+ </div>
28880
+ </div>
28881
+ </div>
28882
+
28883
+ <!-- Tile 4: messages (right-upper, medium 88px) -->
28884
+ <div class="tile-entry tile-entry--4">
28885
+ <div class="icon-tile icon-tile--medium icon-tile--4">
28886
+ <div class="icon-tile__icon">
28887
+ <svg viewBox="0 0 24 24" fill="none">
28888
+ <path d="M7.291 20.824L2 22l1.176-5.291A9.956 9.956 0 0 1 2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10a9.956 9.956 0 0 1-4.709-1.176zM7.581 18.71l.29.158A7.956 7.956 0 0 0 12 20c4.418 0 8-3.582 8-8s-3.582-8-8-8-8 3.582-8 8c0 1.458.39 2.823 1.131 4.13l.159.288-.69 3.1 3.1-.69z" fill="currentColor"/>
28889
+ </svg>
28890
+ </div>
28891
+ </div>
28892
+ </div>
28893
+
28894
+ <!-- Tile 5: home (left-middle, medium 88px) -->
28895
+ <div class="tile-entry tile-entry--5">
28896
+ <div class="icon-tile icon-tile--medium icon-tile--5">
28897
+ <div class="icon-tile__icon">
28898
+ <svg viewBox="0 0 24 24" fill="none">
28899
+ <path d="M19 21H5a1 1 0 0 1-1-1v-9H1l10.327-9.388a1 1 0 0 1 1.346 0L23 11h-3v9a1 1 0 0 1-1 1zM6 19h12v-9.157l-6-5.454-6 5.454V19zm2-4h8v2H8v-2z" fill="currentColor"/>
28900
+ </svg>
28901
+ </div>
28902
+ </div>
28903
+ </div>
28904
+
28905
+ <!-- Hero: bell icon (center, 120px) -->
28906
+ <div class="tile-entry tile-entry--hero">
28907
+ <div class="icon-tile icon-tile--hero">
28908
+ <div class="icon-tile__icon">
28909
+ <svg viewBox="0 0 24 24" fill="none">
28910
+ <path d="M20 17h2v2H2v-2h2v-7a8 8 0 1 1 16 0v7zm-2 0v-7a6 6 0 1 0-12 0v7h12zm-9 4h6v2H9v-2z" fill="currentColor"/>
28911
+ </svg>
28912
+ </div>
28913
+ <span class="icon-tile__badge">18</span>
28914
+ </div>
28915
+ </div>
28916
+
28917
+ </div>
28918
+ </div>
28919
+
28920
+ <!-- Heading + subtitle -->
28921
+ <div class="notification-prompt__texts">
28922
+ <h1 class="notification-prompt__heading">{{ heading() }}</h1>
28923
+ <p class="notification-prompt__subtitle">{{ subtitle() }}</p>
28924
+ </div>
28925
+
28926
+ <!-- CTA buttons -->
28927
+ <div class="notification-prompt__actions">
28928
+ <ds-button
28929
+ variant="primary"
28930
+ size="md"
28931
+ (clicked)="handleAllow()">
28932
+ {{ allowLabel() }}
28933
+ </ds-button>
28934
+ <ds-button
28935
+ class="dismiss-btn"
28936
+ variant="ghost"
28937
+ size="md"
28938
+ (clicked)="handleDismiss()">
28939
+ {{ dismissLabel() }}
28940
+ </ds-button>
28941
+ </div>
28942
+ </div>
28943
+ `, styles: ["@keyframes fadeSlideUp{0%{opacity:0;transform:translateY(32px)}to{opacity:1;transform:translateY(0)}}@keyframes tileEntry{0%{opacity:0;transform:translateY(40px) scale(.85)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes float1{0%{transform:var(--tile-transform) translate(0) rotate(0)}11%{transform:var(--tile-transform) translate(1.4px,-3px) rotate(.9deg)}23%{transform:var(--tile-transform) translate(2.2px,-6.5px) rotate(1.8deg)}37%{transform:var(--tile-transform) translate(.6px,-8.5px) rotate(.4deg)}50%{transform:var(--tile-transform) translate(-1.2px,-7px) rotate(-.8deg)}63%{transform:var(--tile-transform) translate(-1.8px,-4px) rotate(-1.6deg)}76%{transform:var(--tile-transform) translate(-.4px,-1.5px) rotate(-.5deg)}88%{transform:var(--tile-transform) translate(.8px,-.5px) rotate(.3deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float2{0%{transform:var(--tile-transform) translate(0) rotate(0)}12%{transform:var(--tile-transform) translate(-1px,-2.5px) rotate(-.7deg)}25%{transform:var(--tile-transform) translate(-2px,-5.5px) rotate(-1.5deg)}38%{transform:var(--tile-transform) translate(-.8px,-7px) rotate(-.3deg)}50%{transform:var(--tile-transform) translate(1px,-6px) rotate(1deg)}62%{transform:var(--tile-transform) translate(1.6px,-3.5px) rotate(1.6deg)}75%{transform:var(--tile-transform) translate(.5px,-1.5px) rotate(.6deg)}87%{transform:var(--tile-transform) translate(-.5px,-.3px) rotate(-.2deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float3{0%{transform:var(--tile-transform) translate(0) rotate(0)}10%{transform:var(--tile-transform) translate(.8px,-3px) rotate(1.2deg)}22%{transform:var(--tile-transform) translate(1.5px,-7px) rotate(2deg)}35%{transform:var(--tile-transform) translate(.3px,-10px) rotate(.8deg)}48%{transform:var(--tile-transform) translate(-1.5px,-9.5px) rotate(-.6deg)}60%{transform:var(--tile-transform) translate(-2px,-6px) rotate(-1.5deg)}72%{transform:var(--tile-transform) translate(-1px,-3px) rotate(-.8deg)}85%{transform:var(--tile-transform) translate(.3px,-1px) rotate(.3deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float4{0%{transform:var(--tile-transform) translate(0) rotate(0)}13%{transform:var(--tile-transform) translate(-.8px,-2px) rotate(-.8deg)}26%{transform:var(--tile-transform) translate(-1.5px,-5px) rotate(-1.6deg)}40%{transform:var(--tile-transform) translate(.2px,-7.5px) rotate(-.2deg)}52%{transform:var(--tile-transform) translate(1.8px,-6.5px) rotate(1.2deg)}65%{transform:var(--tile-transform) translate(1.2px,-4px) rotate(.8deg)}78%{transform:var(--tile-transform) translate(-.3px,-1.8px) rotate(-.4deg)}90%{transform:var(--tile-transform) translate(-.6px,-.4px) rotate(-.1deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes float5{0%{transform:var(--tile-transform) translate(0) rotate(0)}9%{transform:var(--tile-transform) translate(1.2px,-2px) rotate(.6deg)}20%{transform:var(--tile-transform) translate(2px,-5px) rotate(1.5deg)}33%{transform:var(--tile-transform) translate(.8px,-8.5px) rotate(.5deg)}45%{transform:var(--tile-transform) translate(-1px,-9.5px) rotate(-1deg)}58%{transform:var(--tile-transform) translate(-1.8px,-7px) rotate(-1.8deg)}70%{transform:var(--tile-transform) translate(-.8px,-4px) rotate(-.8deg)}82%{transform:var(--tile-transform) translate(.4px,-1.5px) rotate(.2deg)}92%{transform:var(--tile-transform) translate(.6px,-.3px) rotate(.1deg)}to{transform:var(--tile-transform) translate(0) rotate(0)}}@keyframes floatHero{0%{transform:translate(-50%) translate(0) rotate(0) scale(1)}10%{transform:translate(-50%) translate(.8px,-4px) rotate(.7deg) scale(1.006)}22%{transform:translate(-50%) translate(1.5px,-9px) rotate(1.4deg) scale(1.014)}35%{transform:translate(-50%) translate(.2px,-13px) rotate(.3deg) scale(1.02)}48%{transform:translate(-50%) translate(-1.5px,-12px) rotate(-1deg) scale(1.016)}60%{transform:translate(-50%) translate(-2px,-8px) rotate(-1.6deg) scale(1.01)}72%{transform:translate(-50%) translate(-.6px,-4px) rotate(-.5deg) scale(1.005)}85%{transform:translate(-50%) translate(.5px,-1.5px) rotate(.3deg) scale(1.002)}94%{transform:translate(-50%) translate(.3px,-.3px) rotate(.1deg) scale(1.001)}to{transform:translate(-50%) translate(0) rotate(0) scale(1)}}:host{display:flex;align-items:center;justify-content:center;position:fixed;inset:0;z-index:9999}.notification-prompt{width:100%;max-width:390px;height:100%;display:flex;flex-direction:column;gap:48px;overflow:clip;position:relative}.notification-prompt__backdrop{position:fixed;inset:0;z-index:-1}.notification-prompt__illustration{position:relative;flex:1 0 0;min-height:0;display:flex;align-items:center;justify-content:center;padding:0 24px}.icon-tiles{position:relative;width:100%;max-width:342px;height:100%}.tile-entry{position:absolute;opacity:0;animation:tileEntry .7s cubic-bezier(.22,1,.36,1) forwards;will-change:transform,opacity}.tile-entry--hero{inset:0;animation-delay:.2s;animation-duration:.8s}.tile-entry--1{inset:0;animation-delay:.35s}.tile-entry--4{inset:0;animation-delay:.3s}.tile-entry--5{inset:0;animation-delay:.4s}.tile-entry--3{inset:0;animation-delay:.45s}.tile-entry--2{inset:0;animation-delay:.5s}.icon-tile{position:absolute;display:flex;align-items:center;justify-content:center;border-radius:20%;background:linear-gradient(180deg,#0000 95%,#00000026),linear-gradient(180deg,#fff6,#fff0 4.5%),linear-gradient(180deg,#ffffff26,#fff0),linear-gradient(180deg,#0000,#0000001a),var(--color-accent, #6B5FF5);will-change:transform}.icon-tile__icon{width:62.5%;height:62.5%;display:flex;align-items:center;justify-content:center}.icon-tile__icon svg{width:100%;height:100%;color:#fff;display:block}.icon-tile--1{--tile-transform: translate(-50%, 0) rotate(7.74deg);width:72px;height:72px;top:22%;left:50%;filter:blur(2px);animation:float1 6.8s linear infinite}.icon-tile--2{--tile-transform: rotate(9.27deg);width:88px;height:88px;bottom:6%;right:0;filter:blur(1px);animation:float2 7.4s linear infinite}.icon-tile--3{--tile-transform: rotate(-6.18deg);width:72px;height:72px;bottom:0;left:0;filter:blur(2px);animation:float3 8.6s linear infinite}.icon-tile--4{--tile-transform: rotate(20.57deg);width:88px;height:88px;top:36%;right:0;filter:blur(1px);animation:float4 7.1s linear infinite}.icon-tile--5{--tile-transform: rotate(-8.24deg);width:88px;height:88px;top:44%;left:0;filter:blur(1px);animation:float5 9.2s linear infinite}.icon-tile--hero{width:120px;height:120px;left:50%;bottom:14%;filter:none;border-radius:30px;z-index:2;animation:floatHero 6.2s linear infinite}.icon-tile__badge{position:absolute;top:12.5%;right:12%;min-width:26px;height:26px;padding:0 7px;border-radius:8px;background:var(--content-feedback-bold-red, #b91c1c);display:flex;align-items:center;justify-content:center;font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:18px;line-height:1;letter-spacing:-.04em;color:#fff;white-space:nowrap;box-sizing:border-box}.notification-prompt__texts{position:relative;z-index:1;text-align:center;padding:0 16px;flex-shrink:0;opacity:0;animation:fadeSlideUp .6s .6s cubic-bezier(.22,1,.36,1) forwards}.notification-prompt__heading{font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:32px;line-height:1.15;letter-spacing:-.02em;color:#fff;margin:0 0 4px}.notification-prompt__subtitle{font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:400;font-size:18px;line-height:1.4;letter-spacing:-.02em;color:#eef0ff;margin:0}.notification-prompt__actions{position:relative;z-index:1;display:flex;flex-direction:column;gap:8px;padding:0 24px 24px;padding-bottom:max(24px,env(safe-area-inset-bottom,24px));flex-shrink:0;opacity:0;animation:fadeSlideUp .6s .75s cubic-bezier(.22,1,.36,1) forwards}.notification-prompt__actions ds-button{display:block;width:100%}.notification-prompt__actions ds-button::ng-deep button{width:100%;border-radius:8px}.notification-prompt__actions ds-button.dismiss-btn::ng-deep button{color:#fff;background:transparent}@keyframes exitUp{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-48px)}}@keyframes exitFade{0%{opacity:1}to{opacity:0}}.is-dismissing .notification-prompt__illustration{animation:exitUp .5s cubic-bezier(.55,0,1,.45) forwards}.is-dismissing .notification-prompt__texts{animation:exitUp .45s .15s cubic-bezier(.55,0,1,.45) forwards}.is-dismissing .notification-prompt__actions{animation:exitUp .45s .3s cubic-bezier(.55,0,1,.45) forwards}.is-dismissing .notification-prompt__backdrop{animation:exitFade .35s .45s cubic-bezier(.55,0,1,.45) forwards}@media (prefers-reduced-motion: reduce){.tile-entry,.icon-tile,.icon-tile--hero,.notification-prompt__texts,.notification-prompt__actions{animation:none!important;opacity:1!important}}\n"] }]
28944
+ }], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], allowLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowLabel", required: false }] }], dismissLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissLabel", required: false }] }], allow: [{ type: i0.Output, args: ["allow"] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }] } });
28945
+
28946
+ const NOTIFICATION_ICON_MAP = {
28947
+ inquiry_update: 'remixFileList3Line',
28948
+ inquiry_assigned: 'remixFileList3Line',
28949
+ inquiry_resolved: 'remixFileList3Line',
28950
+ community_post: 'remixCommunityLine',
28951
+ community_comment: 'remixCommunityLine',
28952
+ community_like: 'remixCommunityLine',
28953
+ community_mention: 'remixCommunityLine',
28954
+ booking_confirmed: 'remixCalendarCheckLine',
28955
+ booking_cancelled: 'remixCalendarCheckLine',
28956
+ booking_reminder: 'remixCalendarCheckLine',
28957
+ facility_available: 'remixCalendarCheckLine',
28958
+ message_new: 'remixMessage3Line',
28959
+ message_group: 'remixMessage3Line',
28960
+ service_update: 'remixServiceLine',
28961
+ handbook_update: 'remixBook2Line',
28962
+ system_announcement: 'remixHomeSmile2Line',
28963
+ system_welcome: 'remixHomeSmile2Line',
28964
+ invite_received: 'remixHomeSmile2Line',
28965
+ family_access: 'remixGroupLine',
28966
+ };
28967
+
28968
+ function minutesAgo(m) { return new Date(Date.now() - m * 60_000); }
28969
+ function hoursAgo(h) { return new Date(Date.now() - h * 3_600_000); }
28970
+ function daysAgo(d, hour = 12, minute = 0) {
28971
+ const date = new Date();
28972
+ date.setDate(date.getDate() - d);
28973
+ date.setHours(hour, minute, 0, 0);
28974
+ return date;
28975
+ }
28976
+ const SAMPLE_NOTIFICATIONS = [
28977
+ // Today — inquiry #2 "Problem med vandtryk"
28978
+ { id: '1', type: 'inquiry_update', leading: 'icon', targetId: '2', title: 'Problem med vandtryk', message: 'Din henvendelse er blevet opdateret af ejendomskontoret.', createdAt: minutesAgo(5), read: false },
28979
+ // Today — Mette Larsen commented on post-1
28980
+ { id: '2', type: 'community_comment', leading: 'avatar', targetId: 'post-1', avatarInitials: 'ML', title: 'Mette Larsen', message: 'Kommenterede: "Velkommen til fællesskabet!"', createdAt: minutesAgo(23), read: false },
28981
+ // Today — Marcus Lindqvist sent message in conv-2
28982
+ { id: '3', type: 'message_new', leading: 'avatar', targetId: 'conv-2', avatarInitials: 'ML', title: 'Marcus Lindqvist', message: 'Kan du tjekke støjproblemet igen?', createdAt: minutesAgo(10), read: false },
28983
+ // Today — booking-1 "Festlokale på taget" confirmed
28984
+ { id: '4', type: 'booking_confirmed', leading: 'image', targetId: 'booking-1', imageSrc: '/Assets/Dummy-photos/rooftop-party.jpg', title: 'Festlokale på taget', message: 'Din booking d. 14. feb kl. 9:00–17:00 er bekræftet.', createdAt: hoursAgo(2), read: true },
28985
+ // Today — Anders Jensen commented on post-2
28986
+ { id: '5', type: 'community_comment', leading: 'avatar', targetId: 'post-2', avatarInitials: 'AJ', title: 'Anders Jensen', message: 'Kommenterede: "Wow, den udsigt er fantastisk!"', createdAt: hoursAgo(3), read: true },
28987
+ // Yesterday — Blik Partner A/S assigned to inquiry #2
28988
+ { id: '6', type: 'inquiry_assigned', leading: 'avatar', targetId: '2', avatarInitials: 'BP', title: 'Blik Partner A/S', message: 'Er blevet tildelt din henvendelse om VVS.', createdAt: daysAgo(1, 16, 45), read: true },
28989
+ // Yesterday — Sophie Andersen posted in community (post-2)
28990
+ { id: '7', type: 'community_post', leading: 'avatar', targetId: 'post-2', avatarInitials: 'SA', title: 'Sophie Andersen', message: 'Nyt opslag med billeder fra ejendommen.', createdAt: daysAgo(1, 14, 20), read: true },
28991
+ // Yesterday — booking-3 "Boremaskinen" reminder
28992
+ { id: '8', type: 'booking_reminder', leading: 'image', targetId: 'booking-3', imageSrc: '/Assets/Dummy-photos/handyman.jpg', title: 'Boremaskinen', message: 'Du har en booking d. 25. mar kl. 10:00–14:00.', createdAt: daysAgo(1, 9, 0), read: true },
28993
+ // Yesterday — facility-1 "Boremaskinen" available
28994
+ { id: '9', type: 'facility_available', leading: 'image', targetId: 'facility-1', imageSrc: '/Assets/Dummy-photos/handyman.jpg', title: 'Boremaskinen', message: 'Boremaskinen er nu ledig til booking.', createdAt: daysAgo(1, 8, 15), read: true },
28995
+ // Yesterday — group "Vaskeri & møde" message
28996
+ { id: '10', type: 'message_group', leading: 'avatar', targetId: 'conv-group-demo', avatarInitials: 'VM', title: 'Vaskeri & møde', message: 'Anna Berg: "Vi mødes kl. 19 i fællesrummet."', createdAt: daysAgo(1, 19, 30), read: true },
28997
+ // Earlier — inquiry #3 resolved
28998
+ { id: '11', type: 'inquiry_resolved', leading: 'icon', targetId: '3', title: 'Varme virker ikke ordentligt', message: 'Din henvendelse er nu markeret som løst.', createdAt: daysAgo(3), read: true },
28999
+ // Earlier — handbook updated
29000
+ { id: '12', type: 'handbook_update', leading: 'logo', title: 'Håndbog opdateret', message: 'Afsnittet "Forsyninger" er blevet opdateret med ny information.', createdAt: daysAgo(4), read: true },
29001
+ // Earlier — ElektroTek ApS service update
29002
+ { id: '13', type: 'service_update', leading: 'image', targetId: 'v-4', imageSrc: '/Assets/dummy-logos/electrician-logo.svg', title: 'ElektroTek ApS', message: 'El-service er nu tilgængelig via Services.', createdAt: daysAgo(5), read: true },
29003
+ // Earlier — Thomas Hansen commented on post-2
29004
+ { id: '14', type: 'community_comment', leading: 'avatar', targetId: 'post-2', avatarInitials: 'TH', title: 'Thomas Hansen', message: 'Kommenterede: "Smuk! Jeg kan også se byens silhuet fra min lejlighed 🌆"', createdAt: daysAgo(6), read: true },
29005
+ // Earlier — booking-2 "Gæsteparkering" cancelled
29006
+ { id: '15', type: 'booking_cancelled', leading: 'image', targetId: 'booking-2', imageSrc: '/Assets/Dummy-photos/parking.jpg', title: 'Gæsteparkering', message: 'Din booking d. 20. mar kl. 8:00–20:00 er blevet annulleret.', createdAt: daysAgo(7), read: true },
29007
+ // Earlier — system announcement
29008
+ { id: '16', type: 'system_announcement', leading: 'logo', title: 'Systemmeddelelse', message: 'Planlagt vedligeholdelse d. 1. maj kl. 02:00–04:00.', createdAt: daysAgo(8), read: true },
29009
+ // Earlier — Karl Johansson message in conv-4
29010
+ { id: '17', type: 'message_new', leading: 'avatar', targetId: 'conv-4', avatarInitials: 'KJ', title: 'Karl Johansson', message: 'Hej! Er vaskerummet ledigt i morgen kl. 10?', createdAt: daysAgo(9), read: true },
29011
+ // Earlier — Sara Lindqvist family access
29012
+ { id: '18', type: 'family_access', leading: 'avatar', title: 'Sara Lindqvist', avatarInitials: 'SL', message: 'Sara Lindqvist har fået adgang til din konto.', createdAt: daysAgo(10), read: true },
29013
+ // Earlier — system welcome
29014
+ { id: '19', type: 'system_welcome', leading: 'logo', title: 'Velkommen!', message: 'Velkommen til Propbinder – vi glæder os til at hjælpe dig.', createdAt: daysAgo(11), read: true },
29015
+ ];
29016
+
29017
+ class NotificationService {
29018
+ notifications = signal(SAMPLE_NOTIFICATIONS, ...(ngDevMode ? [{ debugName: "notifications" }] : []));
29019
+ unreadCount = computed(() => this.notifications().filter(n => !n.read).length, ...(ngDevMode ? [{ debugName: "unreadCount" }] : []));
29020
+ /**
29021
+ * Add a new notification to the top of the list.
29022
+ * Intended for downstream push integration — call this when a
29023
+ * real push payload arrives via Capacitor's `pushNotificationReceived` listener.
29024
+ */
29025
+ addNotification(item) {
29026
+ this.notifications.update(list => [item, ...list]);
29027
+ }
29028
+ markAsRead(id) {
29029
+ this.notifications.update(list => list.map(n => n.id === id ? { ...n, read: true } : n));
29030
+ }
29031
+ markAllAsRead() {
29032
+ this.notifications.update(list => list.map(n => ({ ...n, read: true })));
29033
+ }
29034
+ /**
29035
+ * Remove a single notification from the list permanently.
29036
+ */
29037
+ removeNotification(id) {
29038
+ this.notifications.update(list => list.filter(n => n.id !== id));
29039
+ }
29040
+ /**
29041
+ * Remove all notifications from the list permanently.
29042
+ */
29043
+ clearAll() {
29044
+ this.notifications.set([]);
29045
+ }
29046
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
29047
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationService, providedIn: 'root' });
29048
+ }
29049
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationService, decorators: [{
29050
+ type: Injectable,
29051
+ args: [{ providedIn: 'root' }]
29052
+ }] });
29053
+
29054
+ class RelativeTimePipe {
29055
+ transform(date) {
29056
+ if (!date)
29057
+ return '';
29058
+ const now = Date.now();
29059
+ const diffMs = now - date.getTime();
29060
+ const diffSec = Math.floor(diffMs / 1000);
29061
+ const diffMin = Math.floor(diffSec / 60);
29062
+ const diffHr = Math.floor(diffMin / 60);
29063
+ const diffDays = Math.floor(diffHr / 24);
29064
+ if (diffSec < 60)
29065
+ return 'Nu';
29066
+ if (diffMin < 60)
29067
+ return `${diffMin} min siden`;
29068
+ if (diffHr < 24)
29069
+ return `${diffHr} ${diffHr === 1 ? 'time' : 'timer'} siden`;
29070
+ const today = new Date();
29071
+ today.setHours(0, 0, 0, 0);
29072
+ const yesterday = new Date(today.getTime() - 86_400_000);
29073
+ if (date.getTime() >= yesterday.getTime() && date.getTime() < today.getTime()) {
29074
+ return `I går, ${pad(date.getHours())}:${pad(date.getMinutes())}`;
29075
+ }
29076
+ if (diffDays < 7)
29077
+ return `${diffDays} dage siden`;
29078
+ const day = date.getDate();
29079
+ const monthNames = ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'];
29080
+ return `${day}. ${monthNames[date.getMonth()]}`;
29081
+ }
29082
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: RelativeTimePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
29083
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: RelativeTimePipe, isStandalone: true, name: "relativeTime" });
29084
+ }
29085
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: RelativeTimePipe, decorators: [{
29086
+ type: Pipe,
29087
+ args: [{ name: 'relativeTime', standalone: true, pure: true }]
29088
+ }] });
29089
+ function pad(n) {
29090
+ return n < 10 ? `0${n}` : `${n}`;
29091
+ }
29092
+ /**
29093
+ * Bucket a date into 'today' | 'yesterday' | 'earlier' for grouping notifications.
29094
+ */
29095
+ function dateBucket(date) {
29096
+ const now = new Date();
29097
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
29098
+ const yesterday = new Date(today.getTime() - 86_400_000);
29099
+ if (date.getTime() >= today.getTime())
29100
+ return 'today';
29101
+ if (date.getTime() >= yesterday.getTime())
29102
+ return 'yesterday';
29103
+ return 'earlier';
29104
+ }
29105
+
29106
+ class DsMobileNotificationModalComponent {
29107
+ modalController = inject(ModalController);
29108
+ notificationService = inject(NotificationService);
29109
+ bottomSheet = inject(DsMobileBottomSheetService);
29110
+ groups = computed(() => {
29111
+ const items = this.notificationService.notifications();
29112
+ if (!items || items.length === 0)
29113
+ return [];
29114
+ const todayItems = [];
29115
+ const yesterdayItems = [];
29116
+ const earlierItems = [];
29117
+ for (const item of items) {
29118
+ const bucket = dateBucket(item.createdAt);
29119
+ if (bucket === 'today')
29120
+ todayItems.push(item);
29121
+ else if (bucket === 'yesterday')
29122
+ yesterdayItems.push(item);
29123
+ else
29124
+ earlierItems.push(item);
29125
+ }
29126
+ const groups = [];
29127
+ if (todayItems.length > 0)
29128
+ groups.push({ label: 'I dag', items: todayItems });
29129
+ if (yesterdayItems.length > 0)
29130
+ groups.push({ label: 'I går', items: yesterdayItems });
29131
+ if (earlierItems.length > 0)
29132
+ groups.push({ label: 'Tidligere', items: earlierItems });
29133
+ return groups;
29134
+ }, ...(ngDevMode ? [{ debugName: "groups" }] : []));
29135
+ iconFor(type) {
29136
+ return NOTIFICATION_ICON_MAP[type] ?? 'remixNotificationLine';
29137
+ }
29138
+ TYPE_LABELS = {
29139
+ inquiry_update: 'Henvendelse',
29140
+ inquiry_assigned: 'Henvendelse',
29141
+ inquiry_resolved: 'Henvendelse',
29142
+ community_post: 'Fællesskab',
29143
+ community_comment: 'Kommentar',
29144
+ community_like: 'Fællesskab',
29145
+ community_mention: 'Omtale',
29146
+ booking_confirmed: 'Booking',
29147
+ booking_cancelled: 'Booking',
29148
+ booking_reminder: 'Booking',
29149
+ facility_available: 'Facilitet',
29150
+ message_new: 'Besked',
29151
+ message_group: 'Gruppebesked',
29152
+ service_update: 'Service',
29153
+ handbook_update: 'Håndbog',
29154
+ system_announcement: 'System',
29155
+ system_welcome: 'System',
29156
+ invite_received: 'Invitation',
29157
+ family_access: 'Familie',
29158
+ };
29159
+ labelFor(type) {
29160
+ return this.TYPE_LABELS[type] ?? '';
29161
+ }
29162
+ handleNotificationClick(item) {
29163
+ this.modalController.dismiss(item);
29164
+ }
29165
+ async handleLongPress(item) {
29166
+ const actions = [
29167
+ { action: 'mark_read', title: item.read ? 'Marker som ulæst' : 'Marker som læst', icon: item.read ? 'remixMailUnreadLine' : 'remixMailOpenLine', destructive: false },
29168
+ { action: 'remove', title: 'Fjern notifikation', icon: 'remixDeleteBinLine', destructive: true },
29169
+ ];
29170
+ const sheet = await this.bottomSheet.create({
29171
+ component: DsMobileActionsBottomSheetComponent,
29172
+ componentProps: { customActionGroups: [{ actions }] },
29173
+ breakpoints: [0, 1],
29174
+ initialBreakpoint: 1,
29175
+ handle: true,
29176
+ backdropDismiss: true,
29177
+ cssClass: 'auto-height',
29178
+ });
29179
+ const result = await sheet.onWillDismiss();
29180
+ if (result.role === 'select' && result.data) {
29181
+ const action = result.data.action;
29182
+ if (action === 'mark_read') {
29183
+ if (item.read) {
29184
+ this.notificationService.notifications.update(list => list.map(n => n.id === item.id ? { ...n, read: false } : n));
29185
+ }
29186
+ else {
29187
+ this.notificationService.markAsRead(item.id);
29188
+ }
29189
+ }
29190
+ else if (action === 'remove') {
29191
+ this.notificationService.removeNotification(item.id);
29192
+ }
29193
+ }
29194
+ }
29195
+ async openBulkActions() {
29196
+ const actions = [
29197
+ { action: 'mark_all_read', title: 'Marker alle som læst', icon: 'remixMailOpenLine', destructive: false },
29198
+ { action: 'clear_all', title: 'Ryd alle notifikationer', icon: 'remixDeleteBinLine', destructive: true },
29199
+ ];
29200
+ const sheet = await this.bottomSheet.create({
29201
+ component: DsMobileActionsBottomSheetComponent,
29202
+ componentProps: { customActionGroups: [{ actions }] },
29203
+ breakpoints: [0, 1],
29204
+ initialBreakpoint: 1,
29205
+ handle: true,
29206
+ backdropDismiss: true,
29207
+ cssClass: 'auto-height',
29208
+ });
29209
+ const result = await sheet.onWillDismiss();
29210
+ if (result.role === 'select' && result.data) {
29211
+ const action = result.data.action;
29212
+ if (action === 'mark_all_read') {
29213
+ this.notificationService.markAllAsRead();
29214
+ }
29215
+ else if (action === 'clear_all') {
29216
+ this.notificationService.clearAll();
29217
+ }
29218
+ }
29219
+ }
29220
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
29221
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileNotificationModalComponent, isStandalone: true, selector: "ds-mobile-notification-modal", ngImport: i0, template: `
29222
+ <ds-mobile-modal-base
29223
+ headerTitle="Notifikationer"
29224
+ [showHeader]="true"
29225
+ closeButtonLabel="Luk">
29226
+
29227
+ <ds-icon-button
29228
+ header-trailing
29229
+ class="more-button"
29230
+ icon="remixMoreFill"
29231
+ variant="secondary"
29232
+ size="lg"
29233
+ (clicked)="openBulkActions()"
29234
+ aria-label="Flere handlinger"
29235
+ />
29236
+
29237
+ @if (groups().length > 0) {
29238
+ @for (group of groups(); track group.label) {
29239
+ <div class="notification-group">
29240
+ <div class="notification-group__label">{{ group.label }}</div>
29241
+
29242
+ @for (item of group.items; track item.id) {
29243
+ <ds-mobile-list-item
29244
+ [interactive]="true"
29245
+ [showDivider]="!$last"
29246
+ [enableLongPress]="true"
29247
+ [showDesktopMoreButton]="false"
29248
+ align="top"
29249
+ (itemClick)="handleNotificationClick(item)"
29250
+ (longPress)="handleLongPress(item)">
29251
+
29252
+ @if (item.leading === 'icon') {
29253
+ <ds-avatar
29254
+ content-leading
29255
+ type="icon"
29256
+ [iconName]="item.iconName || iconFor(item.type)"
29257
+ size="md"
29258
+ />
29259
+ }
29260
+
29261
+ @if (item.leading === 'avatar') {
29262
+ <ds-avatar
29263
+ content-leading
29264
+ [type]="item.avatarSrc ? 'photo' : 'initials'"
29265
+ [initials]="item.avatarInitials || ''"
29266
+ [src]="item.avatarSrc || ''"
29267
+ size="md"
29268
+ />
29269
+ }
29270
+
29271
+ @if (item.leading === 'image') {
29272
+ <div content-leading class="notification-image">
29273
+ <img [src]="item.imageSrc" [alt]="item.title" />
29274
+ </div>
29275
+ }
29276
+
29277
+ @if (item.leading === 'logo') {
29278
+ <div content-leading class="notification-logo">
29279
+ <ds-app-icon size="lg" />
29280
+ </div>
29281
+ }
29282
+
29283
+ <div content-main class="notification-content">
29284
+ <div class="notification-header">
29285
+ <div class="notification-header__details">
29286
+ <span class="notification-header__name" [class.notification-header__name--unread]="!item.read">
29287
+ {{ item.title }}
29288
+ </span>
29289
+ <span class="notification-header__meta">
29290
+ {{ labelFor(item.type) }}
29291
+ <span class="notification-header__separator">&middot;</span>
29292
+ {{ item.createdAt | relativeTime }}
29293
+ </span>
29294
+ </div>
29295
+ </div>
29296
+ <p class="notification-message">{{ item.message }}</p>
29297
+ </div>
29298
+
29299
+ @if (!item.read) {
29300
+ <div content-trailing class="notification-unread-dot"></div>
29301
+ }
29302
+ </ds-mobile-list-item>
29303
+ }
29304
+ </div>
29305
+ }
29306
+ } @else {
29307
+ <ds-mobile-empty-state
29308
+ imageSrc="/Assets/empty-state-notification.svg"
29309
+ imageAlt="Ingen notifikationer"
29310
+ title="Alt er stille herinde"
29311
+ description="Når der sker noget nyt, finder du det her."
29312
+ />
29313
+ }
29314
+ </ds-mobile-modal-base>
29315
+ `, isInline: true, styles: [":host ::ng-deep .header-content{gap:8px!important}.more-button{flex-shrink:0;border-radius:50%}.more-button::ng-deep button{border-radius:50%!important;width:36px!important;height:36px!important;min-width:36px!important;min-height:36px!important;padding:0!important;display:flex!important;align-items:center!important;justify-content:center!important}.notification-group{padding:0 20px}.notification-group+.notification-group{margin-top:8px;border-top:1px solid var(--color-border-default-secondary, #E5E7EB)}.notification-group__label{font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:13px;line-height:1;letter-spacing:-.02em;color:var(--text-color-default-tertiary, #999);text-transform:none;padding:16px 0 4px}.notification-content{display:flex;flex-direction:column;gap:2px;min-width:0}.notification-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}.notification-header__details{display:flex;flex-direction:column;align-items:flex-start;gap:2px;flex:1;min-width:0}.notification-header__name{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:600;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.notification-header__name--unread{font-weight:600}.notification-header__meta{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs, 12px);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:var(--text-color-default-tertiary, #737373);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:4px}.notification-header__separator{color:var(--text-color-default-tertiary, #737373);opacity:.5}.notification-message{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545B66);margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.notification-image{width:32px;height:32px;border-radius:8px;overflow:hidden;flex-shrink:0}.notification-image img{width:100%;height:100%;-o-object-fit:cover;object-fit:cover;display:block}.notification-logo{flex-shrink:0}.notification-unread-dot{width:8px;height:8px;border-radius:50%;background:var(--color-accent, #6B5FF5);flex-shrink:0;margin-top:6px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsMobileModalBaseComponent, selector: "ds-mobile-modal-base", inputs: ["headerTitleInteractive", "showHeader"], outputs: ["titleClick"] }, { kind: "component", type: DsMobileListItemComponent, selector: "ds-mobile-list-item", inputs: ["leadingSize", "variant", "align", "flushTop", "interactive", "disabled", "loading", "enableLongPress", "showDesktopMoreButton", "moreActions", "moreButtonAriaLabel", "interactiveOffset", "title", "subtitle", "showDivider", "dividerSpacing"], outputs: ["itemClick", "moreButtonClick"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsAppIconComponent, selector: "ds-app-icon", inputs: ["size"] }, { kind: "component", type: DsMobileEmptyStateComponent, selector: "ds-mobile-empty-state", inputs: ["imageSrc", "imageAlt", "title", "description"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "pipe", type: RelativeTimePipe, name: "relativeTime" }] });
29316
+ }
29317
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationModalComponent, decorators: [{
29318
+ type: Component,
29319
+ args: [{ selector: 'ds-mobile-notification-modal', standalone: true, imports: [CommonModule, DsMobileModalBaseComponent, DsMobileListItemComponent, DsAvatarComponent, DsAppIconComponent, DsMobileEmptyStateComponent, DsIconButtonComponent, RelativeTimePipe], template: `
29320
+ <ds-mobile-modal-base
29321
+ headerTitle="Notifikationer"
29322
+ [showHeader]="true"
29323
+ closeButtonLabel="Luk">
29324
+
29325
+ <ds-icon-button
29326
+ header-trailing
29327
+ class="more-button"
29328
+ icon="remixMoreFill"
29329
+ variant="secondary"
29330
+ size="lg"
29331
+ (clicked)="openBulkActions()"
29332
+ aria-label="Flere handlinger"
29333
+ />
29334
+
29335
+ @if (groups().length > 0) {
29336
+ @for (group of groups(); track group.label) {
29337
+ <div class="notification-group">
29338
+ <div class="notification-group__label">{{ group.label }}</div>
29339
+
29340
+ @for (item of group.items; track item.id) {
29341
+ <ds-mobile-list-item
29342
+ [interactive]="true"
29343
+ [showDivider]="!$last"
29344
+ [enableLongPress]="true"
29345
+ [showDesktopMoreButton]="false"
29346
+ align="top"
29347
+ (itemClick)="handleNotificationClick(item)"
29348
+ (longPress)="handleLongPress(item)">
29349
+
29350
+ @if (item.leading === 'icon') {
29351
+ <ds-avatar
29352
+ content-leading
29353
+ type="icon"
29354
+ [iconName]="item.iconName || iconFor(item.type)"
29355
+ size="md"
29356
+ />
29357
+ }
29358
+
29359
+ @if (item.leading === 'avatar') {
29360
+ <ds-avatar
29361
+ content-leading
29362
+ [type]="item.avatarSrc ? 'photo' : 'initials'"
29363
+ [initials]="item.avatarInitials || ''"
29364
+ [src]="item.avatarSrc || ''"
29365
+ size="md"
29366
+ />
29367
+ }
29368
+
29369
+ @if (item.leading === 'image') {
29370
+ <div content-leading class="notification-image">
29371
+ <img [src]="item.imageSrc" [alt]="item.title" />
29372
+ </div>
29373
+ }
29374
+
29375
+ @if (item.leading === 'logo') {
29376
+ <div content-leading class="notification-logo">
29377
+ <ds-app-icon size="lg" />
29378
+ </div>
29379
+ }
29380
+
29381
+ <div content-main class="notification-content">
29382
+ <div class="notification-header">
29383
+ <div class="notification-header__details">
29384
+ <span class="notification-header__name" [class.notification-header__name--unread]="!item.read">
29385
+ {{ item.title }}
29386
+ </span>
29387
+ <span class="notification-header__meta">
29388
+ {{ labelFor(item.type) }}
29389
+ <span class="notification-header__separator">&middot;</span>
29390
+ {{ item.createdAt | relativeTime }}
29391
+ </span>
29392
+ </div>
29393
+ </div>
29394
+ <p class="notification-message">{{ item.message }}</p>
29395
+ </div>
29396
+
29397
+ @if (!item.read) {
29398
+ <div content-trailing class="notification-unread-dot"></div>
29399
+ }
29400
+ </ds-mobile-list-item>
29401
+ }
29402
+ </div>
29403
+ }
29404
+ } @else {
29405
+ <ds-mobile-empty-state
29406
+ imageSrc="/Assets/empty-state-notification.svg"
29407
+ imageAlt="Ingen notifikationer"
29408
+ title="Alt er stille herinde"
29409
+ description="Når der sker noget nyt, finder du det her."
29410
+ />
29411
+ }
29412
+ </ds-mobile-modal-base>
29413
+ `, styles: [":host ::ng-deep .header-content{gap:8px!important}.more-button{flex-shrink:0;border-radius:50%}.more-button::ng-deep button{border-radius:50%!important;width:36px!important;height:36px!important;min-width:36px!important;min-height:36px!important;padding:0!important;display:flex!important;align-items:center!important;justify-content:center!important}.notification-group{padding:0 20px}.notification-group+.notification-group{margin-top:8px;border-top:1px solid var(--color-border-default-secondary, #E5E7EB)}.notification-group__label{font-family:Brockmann,system-ui,-apple-system,sans-serif;font-weight:600;font-size:13px;line-height:1;letter-spacing:-.02em;color:var(--text-color-default-tertiary, #999);text-transform:none;padding:16px 0 4px}.notification-content{display:flex;flex-direction:column;gap:2px;min-width:0}.notification-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}.notification-header__details{display:flex;flex-direction:column;align-items:flex-start;gap:2px;flex:1;min-width:0}.notification-header__name{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:600;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.notification-header__name--unread{font-weight:600}.notification-header__meta{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs, 12px);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:var(--text-color-default-tertiary, #737373);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:4px}.notification-header__separator{color:var(--text-color-default-tertiary, #737373);opacity:.5}.notification-message{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545B66);margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.notification-image{width:32px;height:32px;border-radius:8px;overflow:hidden;flex-shrink:0}.notification-image img{width:100%;height:100%;-o-object-fit:cover;object-fit:cover;display:block}.notification-logo{flex-shrink:0}.notification-unread-dot{width:8px;height:8px;border-radius:50%;background:var(--color-accent, #6B5FF5);flex-shrink:0;margin-top:6px}\n"] }]
29414
+ }] });
28626
29415
 
28627
29416
  /**
28628
29417
  * PostsService
@@ -29149,6 +29938,614 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
29149
29938
  }]
29150
29939
  }], ctorParameters: () => [] });
29151
29940
 
29941
+ class DsMobileBookingDetailSheetComponent {
29942
+ modalController;
29943
+ /** `sheet` = bottom sheet. `modal` = `ds-modal-base` shell (active + history bookings). */
29944
+ presentation = 'sheet';
29945
+ data;
29946
+ /** When true the modal sizes to its content instead of filling the screen. */
29947
+ autoHeight = false;
29948
+ get isModalPresentation() {
29949
+ return this.presentation === 'modal';
29950
+ }
29951
+ constructor(modalController) {
29952
+ this.modalController = modalController;
29953
+ }
29954
+ close() {
29955
+ this.modalController.dismiss(null, 'backdrop');
29956
+ }
29957
+ cancelBooking() {
29958
+ this.modalController.dismiss(null, 'cancel');
29959
+ }
29960
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetComponent, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Component });
29961
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileBookingDetailSheetComponent, isStandalone: true, selector: "ds-mobile-booking-detail-sheet", inputs: { presentation: "presentation", data: "data", autoHeight: "autoHeight" }, host: { properties: { "class.presentation-modal": "this.isModalPresentation" } }, ngImport: i0, template: `
29962
+ @if (presentation === 'modal') {
29963
+ <ds-mobile-modal-base
29964
+ [headerTitle]="data.facilityTitle"
29965
+ [closeButtonLabel]="'Luk'"
29966
+ [isAutoHeight]="autoHeight"
29967
+ [keyboardContentBehavior]="'follow'">
29968
+
29969
+ <ds-mobile-section
29970
+ [showBorder]="false"
29971
+ padding="20px 20px 0 20px">
29972
+ <div class="hero-image-container">
29973
+ @if (data.heroImage) {
29974
+ <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
29975
+ } @else {
29976
+ <div class="hero-image-placeholder">
29977
+ <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
29978
+ </div>
29979
+ }
29980
+ </div>
29981
+ </ds-mobile-section>
29982
+
29983
+ <ds-mobile-section padding="20px 20px 20px 20px">
29984
+ <div class="booking-summary-card">
29985
+ <div class="details-section">
29986
+ <h3 class="details-heading">Booking detaljer</h3>
29987
+ <div class="info-rows">
29988
+ @if (data.bookingDate) {
29989
+ <div class="info-row">
29990
+ <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
29991
+ <span>{{ data.bookingDate }}</span>
29992
+ </div>
29993
+ }
29994
+ @if (data.bookingTime) {
29995
+ <div class="info-row">
29996
+ <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
29997
+ <span>{{ data.bookingTime }}</span>
29998
+ </div>
29999
+ }
30000
+ @if (data.price) {
30001
+ <div class="info-row">
30002
+ <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
30003
+ <span>{{ data.price }}</span>
30004
+ </div>
30005
+ }
30006
+ @if (data.bookingType) {
30007
+ <div class="info-row">
30008
+ <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
30009
+ <span>{{ data.bookingType }}</span>
30010
+ </div>
30011
+ }
30012
+ @for (req of data.requirements || []; track req) {
30013
+ <div class="info-row">
30014
+ <ds-icon name="remixLockLine" size="16px" color="tertiary" />
30015
+ <span>{{ req }}</span>
30016
+ </div>
30017
+ }
30018
+ </div>
30019
+ </div>
30020
+ </div>
30021
+ </ds-mobile-section>
30022
+
30023
+ @if (data.canCancel) {
30024
+ <div class="booking-action">
30025
+ <div class="booking-actions-row">
30026
+ <ds-button
30027
+ class="cancel-primary"
30028
+ size="md"
30029
+ variant="secondary"
30030
+ (clicked)="cancelBooking()">
30031
+ Annuller booking
30032
+ </ds-button>
30033
+ </div>
30034
+ </div>
30035
+ }
30036
+ </ds-mobile-modal-base>
30037
+ } @else {
30038
+ <ds-mobile-bottom-sheet-wrapper [showDragHandle]="true">
30039
+ <div class="detail-header">
30040
+ <h2 class="detail-title">{{ data.facilityTitle }}</h2>
30041
+ <ds-icon-button
30042
+ icon="remixCloseLine"
30043
+ variant="ghost"
30044
+ size="sm"
30045
+ (clicked)="close()"
30046
+ aria-label="Luk"
30047
+ />
30048
+ </div>
30049
+
30050
+ <div class="detail-content">
30051
+ <div class="hero-image-container">
30052
+ @if (data.heroImage) {
30053
+ <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
30054
+ } @else {
30055
+ <div class="hero-image-placeholder">
30056
+ <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
30057
+ </div>
30058
+ }
30059
+ </div>
30060
+
30061
+ <div class="booking-summary-card">
30062
+ <div class="details-section">
30063
+ <h3 class="details-heading">Booking detaljer</h3>
30064
+ <div class="info-rows">
30065
+ @if (data.bookingDate) {
30066
+ <div class="info-row">
30067
+ <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
30068
+ <span>{{ data.bookingDate }}</span>
30069
+ </div>
30070
+ }
30071
+ @if (data.bookingTime) {
30072
+ <div class="info-row">
30073
+ <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
30074
+ <span>{{ data.bookingTime }}</span>
30075
+ </div>
30076
+ }
30077
+ @if (data.price) {
30078
+ <div class="info-row">
30079
+ <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
30080
+ <span>{{ data.price }}</span>
30081
+ </div>
30082
+ }
30083
+ @if (data.bookingType) {
30084
+ <div class="info-row">
30085
+ <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
30086
+ <span>{{ data.bookingType }}</span>
30087
+ </div>
30088
+ }
30089
+ @for (req of data.requirements || []; track req) {
30090
+ <div class="info-row">
30091
+ <ds-icon name="remixLockLine" size="16px" color="tertiary" />
30092
+ <span>{{ req }}</span>
30093
+ </div>
30094
+ }
30095
+ </div>
30096
+ </div>
30097
+ </div>
30098
+
30099
+ @if (data.canCancel) {
30100
+ <div class="footer-button-container">
30101
+ <div class="booking-actions-column">
30102
+ <ds-button
30103
+ size="md"
30104
+ variant="secondary"
30105
+ (clicked)="cancelBooking()">
30106
+ Annuller booking
30107
+ </ds-button>
30108
+ </div>
30109
+ </div>
30110
+ }
30111
+ </div>
30112
+ </ds-mobile-bottom-sheet-wrapper>
30113
+ }
30114
+ `, isInline: true, styles: [":host{display:block;height:auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;box-sizing:border-box}.detail-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 8px}.detail-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:12px}.detail-content{padding:8px 20px 24px;display:flex;flex-direction:column;gap:20px}.hero-image-container{width:100%;aspect-ratio:16 / 9;border-radius:12px;overflow:hidden;background:var(--color-surface-secondary, #f5f5f5)}.hero-image{width:100%;height:100%;object-fit:cover;display:block}.hero-image-placeholder{width:100%;height:100%;min-height:0;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,color-mix(in srgb,var(--color-accent, #6B5FF5) 30%,transparent),color-mix(in srgb,var(--color-accent, #6B5FF5) 60%,transparent))}.booking-summary-card{background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:16px;padding:16px}.details-section{display:flex;flex-direction:column;gap:12px}.details-heading{font-family:Brockmann,sans-serif;font-size:var(--font-size-base, 16px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0}.info-rows{display:flex;flex-direction:column;gap:8px}.info-row{display:flex;align-items:center;gap:10px;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);color:var(--text-color-default-secondary, #545B66);line-height:1.4}.info-row ds-icon{flex-shrink:0}.footer-button-container{width:100%;padding:16px 20px 0}.footer-button-container ::ng-deep ds-button{display:block;width:100%}.footer-button-container ::ng-deep ds-button button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px}.booking-action{padding:16px 20px;padding-bottom:calc(16px + max(8px,env(safe-area-inset-bottom,0px) - 24px));background:var(--color-surface-primary, #ffffff)}.booking-actions-row{display:flex;flex-direction:row;align-items:center;gap:12px;width:100%}.booking-action .cancel-primary{flex:1;min-width:0;display:block}.booking-action .cancel-primary ::ng-deep button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px;padding-left:16px;padding-right:16px}:host.presentation-modal{display:block;height:auto;width:100%;min-height:0;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsMobileBottomSheetWrapperComponent, selector: "ds-mobile-bottom-sheet-wrapper", inputs: ["showDragHandle"] }, { kind: "component", type: DsMobileModalBaseComponent, selector: "ds-mobile-modal-base", inputs: ["headerTitleInteractive", "showHeader"], outputs: ["titleClick"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }] });
30115
+ }
30116
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetComponent, decorators: [{
30117
+ type: Component,
30118
+ args: [{ selector: 'ds-mobile-booking-detail-sheet', standalone: true, imports: [
30119
+ CommonModule,
30120
+ DsMobileBottomSheetWrapperComponent,
30121
+ DsMobileModalBaseComponent,
30122
+ DsMobileSectionComponent,
30123
+ DsButtonComponent,
30124
+ DsIconComponent,
30125
+ DsIconButtonComponent,
30126
+ ], template: `
30127
+ @if (presentation === 'modal') {
30128
+ <ds-mobile-modal-base
30129
+ [headerTitle]="data.facilityTitle"
30130
+ [closeButtonLabel]="'Luk'"
30131
+ [isAutoHeight]="autoHeight"
30132
+ [keyboardContentBehavior]="'follow'">
30133
+
30134
+ <ds-mobile-section
30135
+ [showBorder]="false"
30136
+ padding="20px 20px 0 20px">
30137
+ <div class="hero-image-container">
30138
+ @if (data.heroImage) {
30139
+ <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
30140
+ } @else {
30141
+ <div class="hero-image-placeholder">
30142
+ <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
30143
+ </div>
30144
+ }
30145
+ </div>
30146
+ </ds-mobile-section>
30147
+
30148
+ <ds-mobile-section padding="20px 20px 20px 20px">
30149
+ <div class="booking-summary-card">
30150
+ <div class="details-section">
30151
+ <h3 class="details-heading">Booking detaljer</h3>
30152
+ <div class="info-rows">
30153
+ @if (data.bookingDate) {
30154
+ <div class="info-row">
30155
+ <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
30156
+ <span>{{ data.bookingDate }}</span>
30157
+ </div>
30158
+ }
30159
+ @if (data.bookingTime) {
30160
+ <div class="info-row">
30161
+ <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
30162
+ <span>{{ data.bookingTime }}</span>
30163
+ </div>
30164
+ }
30165
+ @if (data.price) {
30166
+ <div class="info-row">
30167
+ <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
30168
+ <span>{{ data.price }}</span>
30169
+ </div>
30170
+ }
30171
+ @if (data.bookingType) {
30172
+ <div class="info-row">
30173
+ <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
30174
+ <span>{{ data.bookingType }}</span>
30175
+ </div>
30176
+ }
30177
+ @for (req of data.requirements || []; track req) {
30178
+ <div class="info-row">
30179
+ <ds-icon name="remixLockLine" size="16px" color="tertiary" />
30180
+ <span>{{ req }}</span>
30181
+ </div>
30182
+ }
30183
+ </div>
30184
+ </div>
30185
+ </div>
30186
+ </ds-mobile-section>
30187
+
30188
+ @if (data.canCancel) {
30189
+ <div class="booking-action">
30190
+ <div class="booking-actions-row">
30191
+ <ds-button
30192
+ class="cancel-primary"
30193
+ size="md"
30194
+ variant="secondary"
30195
+ (clicked)="cancelBooking()">
30196
+ Annuller booking
30197
+ </ds-button>
30198
+ </div>
30199
+ </div>
30200
+ }
30201
+ </ds-mobile-modal-base>
30202
+ } @else {
30203
+ <ds-mobile-bottom-sheet-wrapper [showDragHandle]="true">
30204
+ <div class="detail-header">
30205
+ <h2 class="detail-title">{{ data.facilityTitle }}</h2>
30206
+ <ds-icon-button
30207
+ icon="remixCloseLine"
30208
+ variant="ghost"
30209
+ size="sm"
30210
+ (clicked)="close()"
30211
+ aria-label="Luk"
30212
+ />
30213
+ </div>
30214
+
30215
+ <div class="detail-content">
30216
+ <div class="hero-image-container">
30217
+ @if (data.heroImage) {
30218
+ <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
30219
+ } @else {
30220
+ <div class="hero-image-placeholder">
30221
+ <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
30222
+ </div>
30223
+ }
30224
+ </div>
30225
+
30226
+ <div class="booking-summary-card">
30227
+ <div class="details-section">
30228
+ <h3 class="details-heading">Booking detaljer</h3>
30229
+ <div class="info-rows">
30230
+ @if (data.bookingDate) {
30231
+ <div class="info-row">
30232
+ <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
30233
+ <span>{{ data.bookingDate }}</span>
30234
+ </div>
30235
+ }
30236
+ @if (data.bookingTime) {
30237
+ <div class="info-row">
30238
+ <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
30239
+ <span>{{ data.bookingTime }}</span>
30240
+ </div>
30241
+ }
30242
+ @if (data.price) {
30243
+ <div class="info-row">
30244
+ <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
30245
+ <span>{{ data.price }}</span>
30246
+ </div>
30247
+ }
30248
+ @if (data.bookingType) {
30249
+ <div class="info-row">
30250
+ <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
30251
+ <span>{{ data.bookingType }}</span>
30252
+ </div>
30253
+ }
30254
+ @for (req of data.requirements || []; track req) {
30255
+ <div class="info-row">
30256
+ <ds-icon name="remixLockLine" size="16px" color="tertiary" />
30257
+ <span>{{ req }}</span>
30258
+ </div>
30259
+ }
30260
+ </div>
30261
+ </div>
30262
+ </div>
30263
+
30264
+ @if (data.canCancel) {
30265
+ <div class="footer-button-container">
30266
+ <div class="booking-actions-column">
30267
+ <ds-button
30268
+ size="md"
30269
+ variant="secondary"
30270
+ (clicked)="cancelBooking()">
30271
+ Annuller booking
30272
+ </ds-button>
30273
+ </div>
30274
+ </div>
30275
+ }
30276
+ </div>
30277
+ </ds-mobile-bottom-sheet-wrapper>
30278
+ }
30279
+ `, styles: [":host{display:block;height:auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;box-sizing:border-box}.detail-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 8px}.detail-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:12px}.detail-content{padding:8px 20px 24px;display:flex;flex-direction:column;gap:20px}.hero-image-container{width:100%;aspect-ratio:16 / 9;border-radius:12px;overflow:hidden;background:var(--color-surface-secondary, #f5f5f5)}.hero-image{width:100%;height:100%;object-fit:cover;display:block}.hero-image-placeholder{width:100%;height:100%;min-height:0;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,color-mix(in srgb,var(--color-accent, #6B5FF5) 30%,transparent),color-mix(in srgb,var(--color-accent, #6B5FF5) 60%,transparent))}.booking-summary-card{background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:16px;padding:16px}.details-section{display:flex;flex-direction:column;gap:12px}.details-heading{font-family:Brockmann,sans-serif;font-size:var(--font-size-base, 16px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0}.info-rows{display:flex;flex-direction:column;gap:8px}.info-row{display:flex;align-items:center;gap:10px;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);color:var(--text-color-default-secondary, #545B66);line-height:1.4}.info-row ds-icon{flex-shrink:0}.footer-button-container{width:100%;padding:16px 20px 0}.footer-button-container ::ng-deep ds-button{display:block;width:100%}.footer-button-container ::ng-deep ds-button button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px}.booking-action{padding:16px 20px;padding-bottom:calc(16px + max(8px,env(safe-area-inset-bottom,0px) - 24px));background:var(--color-surface-primary, #ffffff)}.booking-actions-row{display:flex;flex-direction:row;align-items:center;gap:12px;width:100%}.booking-action .cancel-primary{flex:1;min-width:0;display:block}.booking-action .cancel-primary ::ng-deep button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px;padding-left:16px;padding-right:16px}:host.presentation-modal{display:block;height:auto;width:100%;min-height:0;overflow:hidden}\n"] }]
30280
+ }], ctorParameters: () => [{ type: i1.ModalController }], propDecorators: { presentation: [{
30281
+ type: Input
30282
+ }], data: [{
30283
+ type: Input
30284
+ }], autoHeight: [{
30285
+ type: Input
30286
+ }], isModalPresentation: [{
30287
+ type: HostBinding,
30288
+ args: ['class.presentation-modal']
30289
+ }] } });
30290
+
30291
+ class DsMobileBookingDetailSheetService extends BaseModalService {
30292
+ bottomSheet;
30293
+ constructor(modalController, bottomSheet) {
30294
+ super(modalController);
30295
+ this.bottomSheet = bottomSheet;
30296
+ }
30297
+ /**
30298
+ * Bottom-sheet presentation (draggable breakpoints). Prefer `openAsModal` for booking lists.
30299
+ */
30300
+ async open(data) {
30301
+ const modal = await this.bottomSheet.create({
30302
+ component: DsMobileBookingDetailSheetComponent,
30303
+ componentProps: { data },
30304
+ autoHeight: true,
30305
+ backdropDismiss: true,
30306
+ backdropBlur: true,
30307
+ });
30308
+ const result = await modal.onWillDismiss();
30309
+ return { role: result.role, data: result.data ?? undefined };
30310
+ }
30311
+ /** `ds-modal-base` shell — used for active and past bookings. Auto-heights to content. */
30312
+ async openAsModal(data) {
30313
+ const modal = await this.createModal(DsMobileBookingDetailSheetComponent, { data, presentation: 'modal', autoHeight: true }, { keyboardClose: true, autoHeight: true });
30314
+ await modal.present();
30315
+ const result = await modal.onWillDismiss();
30316
+ return { role: result.role, data: result.data ?? undefined };
30317
+ }
30318
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetService, deps: [{ token: i1.ModalController }, { token: DsMobileBottomSheetService }], target: i0.ɵɵFactoryTarget.Injectable });
30319
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetService, providedIn: 'root' });
30320
+ }
30321
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetService, decorators: [{
30322
+ type: Injectable,
30323
+ args: [{
30324
+ providedIn: 'root',
30325
+ }]
30326
+ }], ctorParameters: () => [{ type: i1.ModalController }, { type: DsMobileBottomSheetService }] });
30327
+
30328
+ const VENDOR_MODAL_SERVICE = new InjectionToken('VendorModalService');
30329
+ const BOOKING_DETAIL_MAP = {
30330
+ 'booking-1': {
30331
+ id: 'booking-1',
30332
+ facilityTitle: 'Festlokale på taget',
30333
+ heroImage: '/Assets/Dummy-photos/rooftop-party.jpg',
30334
+ bookingDate: '14. februar',
30335
+ bookingTime: '9:00 - 17:00',
30336
+ price: '200 kr. per booking',
30337
+ canCancel: true,
30338
+ },
30339
+ 'booking-2': {
30340
+ id: 'booking-2',
30341
+ facilityTitle: 'Gæsteparkering',
30342
+ heroImage: '/Assets/Dummy-photos/parking.jpg',
30343
+ bookingDate: '20. marts',
30344
+ bookingTime: '8:00 - 20:00',
30345
+ price: '50 kr. per booking',
30346
+ canCancel: true,
30347
+ },
30348
+ 'booking-3': {
30349
+ id: 'booking-3',
30350
+ facilityTitle: 'Boremaskinen',
30351
+ heroImage: '/Assets/Dummy-photos/handyman.jpg',
30352
+ bookingDate: '25. marts',
30353
+ bookingTime: '10:00 - 14:00',
30354
+ price: 'Gratis',
30355
+ canCancel: true,
30356
+ },
30357
+ };
30358
+ const VENDOR_DETAIL_MAP = {
30359
+ 'v-1': {
30360
+ vendorName: 'CleanTeam ApS',
30361
+ vendorDescription: '<h3>Rengøring</h3><p>CleanTeam ApS tilbyder professionel trappevask og vinduespolering for ejendommen. Servicen udføres ugentligt og kan tilpasses jeres behov.</p><h3>Pris</h3><p>350 kr. per besøg</p>',
30362
+ vendorImage: '/Assets/Dummy-photos/clean-team.jpg',
30363
+ vendorLogo: '/Assets/dummy-logos/cleanteam-logo.svg',
30364
+ },
30365
+ 'v-2': {
30366
+ vendorName: 'Nordisk Rengøring',
30367
+ vendorDescription: '<h3>Rengøring</h3><p>Nordisk Rengøring leverer fleksibel erhvervs- og boligrengøring. Vi tilpasser rengøringen til jeres specifikke behov.</p><h3>Pris</h3><p>275 kr. per time</p>',
30368
+ vendorImage: '/Assets/Dummy-photos/nordic-cleaning.jpg',
30369
+ vendorLogo: '/Assets/dummy-logos/nordiccleaning-logo.svg',
30370
+ },
30371
+ 'v-3': {
30372
+ vendorName: 'Blik Partner A/S',
30373
+ vendorDescription: '<h3>VVS</h3><p>BlikPartner A/S er jeres VVS-partner til alt fra løbende vedligeholdelse til akutte udkald. Vi dækker reparation af vandrør, afløb, radiatorer og blandingsbatterier.</p><h3>Pris</h3><p>450 kr. per udkald + tid</p>',
30374
+ vendorImage: '/Assets/Dummy-photos/plumbing.jpg',
30375
+ },
30376
+ 'v-4': {
30377
+ vendorName: 'ElektroTek ApS',
30378
+ vendorDescription: '<h3>Elektriker</h3><p>ElektroTek ApS varetager el-installationer, fejlsøgning og lovpligtige eftersyn for ejendommen. Vi udfører også småopgaver som udskiftning af kontakter og lampeudtag.</p><h3>Pris</h3><p>395 kr. per time</p>',
30379
+ vendorImage: '/Assets/Dummy-photos/electrician.jpg',
30380
+ vendorLogo: '/Assets/dummy-logos/electrician-logo.svg',
30381
+ },
30382
+ 'v-5': {
30383
+ vendorName: 'HaveService Danmark',
30384
+ vendorDescription: '<h3>Have & Grønne arealer</h3><p>HaveService Danmark står for den løbende pleje af fællesarealer og haver — herunder græsslåning, hækklipning, ukrudtsbekæmpelse og sæsonbeplantning.</p><h3>Pris</h3><p>500 kr. per besøg</p>',
30385
+ vendorImage: '/Assets/Dummy-photos/gardener.jpg',
30386
+ vendorLogo: '/Assets/dummy-logos/gardener-logo.svg',
30387
+ },
30388
+ };
30389
+ class DsMobileNotificationModalService extends BaseModalService {
30390
+ router = inject(Router);
30391
+ notificationService = inject(NotificationService);
30392
+ postsService = inject(PostsService);
30393
+ userService = inject(UserService);
30394
+ postModal = inject(DsMobilePostDetailModalService);
30395
+ peerMessaging = inject(PeerMessagingService);
30396
+ peerChat = inject(PeerChatLauncherService);
30397
+ bookingDetailSheet = inject(DsMobileBookingDetailSheetService);
30398
+ vendorModal = inject(VENDOR_MODAL_SERVICE, { optional: true });
30399
+ constructor(modalController) {
30400
+ super(modalController);
30401
+ }
30402
+ /**
30403
+ * Open the notification modal. The modal reads notifications reactively
30404
+ * from NotificationService, so it updates live when items are marked
30405
+ * read or removed via long-press actions.
30406
+ *
30407
+ * Returns the tapped notification (if the user navigated to one), or null.
30408
+ */
30409
+ async open() {
30410
+ const modal = await this.createModal(DsMobileNotificationModalComponent, {});
30411
+ await modal.present();
30412
+ const result = await modal.onWillDismiss();
30413
+ const tapped = result.data ?? null;
30414
+ if (tapped) {
30415
+ this.notificationService.markAsRead(tapped.id);
30416
+ await this.routeTo(tapped);
30417
+ }
30418
+ return tapped;
30419
+ }
30420
+ /**
30421
+ * Navigate to the screen associated with a notification item.
30422
+ * Public so downstream devs can call it from a Capacitor
30423
+ * `pushNotificationActionPerformed` listener for deep-link handling.
30424
+ */
30425
+ async routeTo(item) {
30426
+ const id = item.targetId;
30427
+ switch (item.type) {
30428
+ case 'inquiry_update':
30429
+ case 'inquiry_assigned':
30430
+ case 'inquiry_resolved':
30431
+ await this.router.navigate(['/inquiry-detail', id ?? '1']);
30432
+ break;
30433
+ case 'community_post':
30434
+ case 'community_comment':
30435
+ case 'community_like':
30436
+ case 'community_mention':
30437
+ if (id) {
30438
+ await this.openPostModal(id);
30439
+ }
30440
+ else {
30441
+ await this.router.navigate(['/announcements']);
30442
+ }
30443
+ break;
30444
+ case 'booking_confirmed':
30445
+ case 'booking_cancelled':
30446
+ case 'booking_reminder':
30447
+ case 'facility_available':
30448
+ if (id) {
30449
+ await this.openBookingDetail(item);
30450
+ }
30451
+ else {
30452
+ await this.router.navigate(['/booking']);
30453
+ }
30454
+ break;
30455
+ case 'message_new':
30456
+ case 'message_group':
30457
+ if (id) {
30458
+ await this.openChat(id);
30459
+ }
30460
+ else {
30461
+ await this.router.navigate(['/messages']);
30462
+ }
30463
+ break;
30464
+ case 'service_update':
30465
+ if (id) {
30466
+ await this.openVendorModal(id);
30467
+ }
30468
+ else {
30469
+ await this.router.navigate(['/services']);
30470
+ }
30471
+ break;
30472
+ case 'handbook_update':
30473
+ await this.router.navigate(['/handbook']);
30474
+ break;
30475
+ case 'family_access':
30476
+ await this.router.navigate(['/family-access']);
30477
+ break;
30478
+ default:
30479
+ await this.router.navigate(['/home']);
30480
+ break;
30481
+ }
30482
+ }
30483
+ async openChat(conversationId) {
30484
+ const conv = this.peerMessaging.conversations().find(c => c.id === conversationId);
30485
+ if (conv) {
30486
+ await this.peerChat.openConversation(conv);
30487
+ }
30488
+ else {
30489
+ await this.router.navigate(['/messages']);
30490
+ }
30491
+ }
30492
+ async openVendorModal(vendorId) {
30493
+ const vendor = VENDOR_DETAIL_MAP[vendorId];
30494
+ if (vendor && this.vendorModal) {
30495
+ await this.vendorModal.open(vendor);
30496
+ }
30497
+ else {
30498
+ await this.router.navigate(['/services']);
30499
+ }
30500
+ }
30501
+ async openBookingDetail(item) {
30502
+ const booking = BOOKING_DETAIL_MAP[item.targetId];
30503
+ if (booking) {
30504
+ await this.bookingDetailSheet.openAsModal(booking);
30505
+ }
30506
+ else {
30507
+ await this.router.navigate(['/booking']);
30508
+ }
30509
+ }
30510
+ async openPostModal(postId) {
30511
+ const post = this.postsService.getPostById(postId);
30512
+ if (!post) {
30513
+ await this.router.navigate(['/announcements']);
30514
+ return;
30515
+ }
30516
+ const data = {
30517
+ postId: post.id,
30518
+ authorName: post.authorName,
30519
+ authorRole: post.authorRole,
30520
+ timestamp: post.timestamp,
30521
+ content: post.content,
30522
+ avatarInitials: post.avatarInitials,
30523
+ avatarType: post.avatarType === 'icon' ? undefined : post.avatarType,
30524
+ avatarSrc: post.avatarSrc,
30525
+ imageSrc: post.imageSrc,
30526
+ imageAlt: post.imageAlt,
30527
+ isLiked: post.isLiked,
30528
+ likeCount: post.likeCount,
30529
+ commentCount: post.commentCount,
30530
+ comments: post.comments,
30531
+ };
30532
+ await this.postModal.open(data, {
30533
+ currentUserName: this.userService.displayName(),
30534
+ currentUserInitials: this.userService.avatarInitials(),
30535
+ });
30536
+ }
30537
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationModalService, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Injectable });
30538
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationModalService, providedIn: 'root' });
30539
+ }
30540
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileNotificationModalService, decorators: [{
30541
+ type: Injectable,
30542
+ args: [{
30543
+ providedIn: 'root',
30544
+ }]
30545
+ }], ctorParameters: () => [{ type: i1.ModalController }] });
30546
+
30547
+ // Mobile Page Components
30548
+
29152
30549
  class MobileCommunityPageComponent {
29153
30550
  router;
29154
30551
  route;
@@ -29159,6 +30556,8 @@ class MobileCommunityPageComponent {
29159
30556
  postsService;
29160
30557
  pageComponent;
29161
30558
  pinnedSwiper;
30559
+ notificationModal = inject(DsMobileNotificationModalService);
30560
+ notificationService = inject(NotificationService);
29162
30561
  // Get posts from service (using computed for safe initialization)
29163
30562
  allPosts = computed(() => this.postsService.posts(), ...(ngDevMode ? [{ debugName: "allPosts" }] : []));
29164
30563
  // Get pinned posts - filter by isPinned flag
@@ -29201,6 +30600,11 @@ class MobileCommunityPageComponent {
29201
30600
  // Complete the infinite scroll
29202
30601
  event.target.complete();
29203
30602
  }
30603
+ async handleNotificationClick() {
30604
+ const tapped = await this.notificationModal.open();
30605
+ if (tapped)
30606
+ console.log('Notification tapped:', tapped.type, tapped.id);
30607
+ }
29204
30608
  handleRefresh(event) {
29205
30609
  console.log('Pull-to-refresh triggered');
29206
30610
  // Check if offline and complete immediately
@@ -29442,8 +30846,10 @@ class MobileCommunityPageComponent {
29442
30846
  <ds-mobile-page-main
29443
30847
  #pageComponent
29444
30848
  title="Fællesskab"
30849
+ [notificationCount]="notificationService.unreadCount()"
29445
30850
  [avatarInitials]="userService.avatarInitials()"
29446
30851
  [avatarType]="userService.avatarType()"
30852
+ (notificationClick)="handleNotificationClick()"
29447
30853
  (refresh)="handleRefresh($event)">
29448
30854
 
29449
30855
  <!-- Offline indicator -->
@@ -29696,7 +31102,7 @@ class MobileCommunityPageComponent {
29696
31102
  }
29697
31103
  </ds-mobile-section>
29698
31104
  </ds-mobile-page-main>
29699
- `, isInline: true, styles: [".pinned-posts-swiper-wrapper{padding:0;position:relative}.swiper-nav-buttons{display:contents}.swiper-nav-button{position:absolute;top:50%;transform:translateY(-50%);z-index:10}.swiper-nav-button:first-child{left:-48px}.swiper-nav-button:last-child{right:-48px}::ng-deep .swiper-nav-button button{border-radius:50%!important;width:48px!important;height:48px!important;padding:0!important}@media (max-width: 767px){.swiper-nav-buttons{display:none}}::ng-deep .pinned-posts-swiper .swiper-slide{width:100%;max-width:600px;height:auto}@media (min-width: 768px){::ng-deep .pinned-posts-swiper .swiper-slide{max-width:100%}}.swiper-post-item{width:100%;height:auto}::ng-deep .pinned-posts-swiper .swiper-slide ds-mobile-interactive-list-item-post{height:auto}::ng-deep .pinned-posts-swiper .swiper-wrapper{height:auto;align-items:flex-start}.post-list-wrapper{display:flex;flex-direction:column}.clickable-image{cursor:pointer;transition:transform .2s ease,opacity .2s ease;border-radius:8px;display:block;width:100%;aspect-ratio:16/9;object-fit:cover}.clickable-image:active{transform:scale(.98);opacity:.9}.community-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-image{width:96px;height:96px;margin-bottom:24px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin:16px 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}ion-infinite-scroll{--color: var(--color-primary-surface)}ion-infinite-scroll-content{--color: var(--color-primary-surface)}ion-infinite-scroll-content::part(spinner){color:var(--color-primary-surface)}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileInteractiveListItemPostComponent, selector: "ds-mobile-interactive-list-item-post", inputs: ["authorName", "authorRole", "timestamp", "avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "showBadge", "variant", "align", "clickable", "enableLongPress", "moreActions"], outputs: ["postClick", "commentClick", "longPress"] }, { kind: "component", type: DsMobilePostComposerComponent, selector: "ds-mobile-post-composer", inputs: ["avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "placeholder", "buttonText"], outputs: ["composerClick"] }, { kind: "component", type: DsMobileSwiperComponent, selector: "ds-mobile-swiper", inputs: ["slideWidth", "gap", "pagination", "autoHeight", "progressiveOpacity", "progressiveScale"] }, { kind: "component", type: PostContentComponent, selector: "post-content" }, { kind: "component", type: PostTextComponent, selector: "post-text" }, { kind: "component", type: PostMediaComponent, selector: "post-media" }, { kind: "component", type: PostAttachmentsComponent, selector: "post-attachments" }, { kind: "component", type: PostActionsComponent, selector: "post-actions" }, { kind: "component", type: ActionLikeComponent, selector: "action-like", inputs: ["active", "count"], outputs: ["activeChange", "countChange", "likeClick"] }, { kind: "component", type: ActionCommentComponent, selector: "action-comment", inputs: ["count"], outputs: ["commentClick"] }, { kind: "component", type: DsMobileCardInlineFileComponent, selector: "ds-mobile-card-inline-file", inputs: ["fileName", "fileSize", "variant", "layout", "fileUrl"], outputs: ["fileClick"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsMobileInlinePhotoComponent, selector: "ds-mobile-inline-photo", inputs: ["images", "loadingStates", "author", "maxVisible", "useGrid"], outputs: ["photoClick"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: IonInfiniteScroll, selector: "ion-infinite-scroll", inputs: ["disabled", "position", "threshold"] }, { kind: "component", type: IonInfiniteScrollContent, selector: "ion-infinite-scroll-content", inputs: ["loadingSpinner", "loadingText"] }] });
31105
+ `, isInline: true, styles: [".pinned-posts-swiper-wrapper{padding:0;position:relative}.swiper-nav-buttons{display:contents}.swiper-nav-button{position:absolute;top:50%;transform:translateY(-50%);z-index:10}.swiper-nav-button:first-child{left:-48px}.swiper-nav-button:last-child{right:-48px}::ng-deep .swiper-nav-button button{border-radius:50%!important;width:48px!important;height:48px!important;padding:0!important}@media (max-width: 767px){.swiper-nav-buttons{display:none}}::ng-deep .pinned-posts-swiper .swiper-slide{width:100%;max-width:600px;height:auto}@media (min-width: 768px){::ng-deep .pinned-posts-swiper .swiper-slide{max-width:100%}}.swiper-post-item{width:100%;height:auto}::ng-deep .pinned-posts-swiper .swiper-slide ds-mobile-interactive-list-item-post{height:auto}::ng-deep .pinned-posts-swiper .swiper-wrapper{height:auto;align-items:flex-start}.post-list-wrapper{display:flex;flex-direction:column}.clickable-image{cursor:pointer;transition:transform .2s ease,opacity .2s ease;border-radius:8px;display:block;width:100%;aspect-ratio:16/9;object-fit:cover}.clickable-image:active{transform:scale(.98);opacity:.9}.community-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-image{width:96px;height:96px;margin-bottom:24px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin:16px 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}ion-infinite-scroll{--color: var(--color-primary-surface)}ion-infinite-scroll-content{--color: var(--color-primary-surface)}ion-infinite-scroll-content::part(spinner){color:var(--color-primary-surface)}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileInteractiveListItemPostComponent, selector: "ds-mobile-interactive-list-item-post", inputs: ["authorName", "authorRole", "timestamp", "avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "showBadge", "variant", "align", "clickable", "enableLongPress", "moreActions"], outputs: ["postClick", "commentClick", "longPress"] }, { kind: "component", type: DsMobilePostComposerComponent, selector: "ds-mobile-post-composer", inputs: ["avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "placeholder", "buttonText"], outputs: ["composerClick"] }, { kind: "component", type: DsMobileSwiperComponent, selector: "ds-mobile-swiper", inputs: ["slideWidth", "gap", "pagination", "autoHeight", "progressiveOpacity", "progressiveScale"] }, { kind: "component", type: PostContentComponent, selector: "post-content" }, { kind: "component", type: PostTextComponent, selector: "post-text" }, { kind: "component", type: PostMediaComponent, selector: "post-media" }, { kind: "component", type: PostAttachmentsComponent, selector: "post-attachments" }, { kind: "component", type: PostActionsComponent, selector: "post-actions" }, { kind: "component", type: ActionLikeComponent, selector: "action-like", inputs: ["active", "count"], outputs: ["activeChange", "countChange", "likeClick"] }, { kind: "component", type: ActionCommentComponent, selector: "action-comment", inputs: ["count"], outputs: ["commentClick"] }, { kind: "component", type: DsMobileCardInlineFileComponent, selector: "ds-mobile-card-inline-file", inputs: ["fileName", "fileSize", "variant", "layout", "fileUrl"], outputs: ["fileClick"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsMobileInlinePhotoComponent, selector: "ds-mobile-inline-photo", inputs: ["images", "loadingStates", "author", "maxVisible", "useGrid"], outputs: ["photoClick"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: IonInfiniteScroll, selector: "ion-infinite-scroll", inputs: ["disabled", "position", "threshold"] }, { kind: "component", type: IonInfiniteScrollContent, selector: "ion-infinite-scroll-content", inputs: ["loadingSpinner", "loadingText"] }] });
29700
31106
  }
29701
31107
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileCommunityPageComponent, decorators: [{
29702
31108
  type: Component,
@@ -29723,8 +31129,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
29723
31129
  <ds-mobile-page-main
29724
31130
  #pageComponent
29725
31131
  title="Fællesskab"
31132
+ [notificationCount]="notificationService.unreadCount()"
29726
31133
  [avatarInitials]="userService.avatarInitials()"
29727
31134
  [avatarType]="userService.avatarType()"
31135
+ (notificationClick)="handleNotificationClick()"
29728
31136
  (refresh)="handleRefresh($event)">
29729
31137
 
29730
31138
  <!-- Offline indicator -->
@@ -29989,6 +31397,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
29989
31397
  class MobileHandbookPageComponent {
29990
31398
  userService;
29991
31399
  pageComponent;
31400
+ notificationModal = inject(DsMobileNotificationModalService);
31401
+ notificationService = inject(NotificationService);
29992
31402
  // Utilities folder data
29993
31403
  utilitiesItems = [
29994
31404
  {
@@ -30231,6 +31641,11 @@ class MobileHandbookPageComponent {
30231
31641
  constructor(userService) {
30232
31642
  this.userService = userService;
30233
31643
  }
31644
+ async handleNotificationClick() {
31645
+ const tapped = await this.notificationModal.open();
31646
+ if (tapped)
31647
+ console.log('Notification tapped:', tapped.type, tapped.id);
31648
+ }
30234
31649
  handleRefresh(event) {
30235
31650
  console.log('Pull-to-refresh triggered');
30236
31651
  // Check if offline and complete immediately
@@ -30246,7 +31661,7 @@ class MobileHandbookPageComponent {
30246
31661
  }
30247
31662
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileHandbookPageComponent, deps: [{ token: UserService }], target: i0.ɵɵFactoryTarget.Component });
30248
31663
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MobileHandbookPageComponent, isStandalone: true, selector: "app-mobile-handbook-page", viewQueries: [{ propertyName: "pageComponent", first: true, predicate: ["pageComponent"], descendants: true }], ngImport: i0, template: `
30249
- <ds-mobile-page-main #pageComponent title="Håndbog" [avatarInitials]="userService.avatarInitials()" [avatarType]="userService.avatarType()" (refresh)="handleRefresh($event)">
31664
+ <ds-mobile-page-main #pageComponent title="Håndbog" [notificationCount]="notificationService.unreadCount()" [avatarInitials]="userService.avatarInitials()" [avatarType]="userService.avatarType()" (notificationClick)="handleNotificationClick()" (refresh)="handleRefresh($event)">
30250
31665
  <!-- Offline indicator -->
30251
31666
  @if (pageComponent.isOffline()) {
30252
31667
  <ds-mobile-offline-banner offline-indicator title="Ingen internetforbindelse" message="Nogle funktioner kan være utilgængelige"> </ds-mobile-offline-banner>
@@ -30267,12 +31682,12 @@ class MobileHandbookPageComponent {
30267
31682
  </div>
30268
31683
  </ds-mobile-section>
30269
31684
  </ds-mobile-page-main>
30270
- `, isInline: true, styles: [".folders-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px;justify-items:center}@media (min-width: 768px){.folders-grid{grid-template-columns:repeat(3,1fr)}}ds-mobile-handbook-folder{width:100%;min-width:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileHandbookFolderComponent, selector: "ds-mobile-handbook-folder", inputs: ["variant", "customColor", "iconName", "itemCount", "label", "items", "loading", "error"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }] });
31685
+ `, isInline: true, styles: [".folders-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px;justify-items:center}@media (min-width: 768px){.folders-grid{grid-template-columns:repeat(3,1fr)}}ds-mobile-handbook-folder{width:100%;min-width:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileHandbookFolderComponent, selector: "ds-mobile-handbook-folder", inputs: ["variant", "customColor", "iconName", "itemCount", "label", "items", "loading", "error"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }] });
30271
31686
  }
30272
31687
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileHandbookPageComponent, decorators: [{
30273
31688
  type: Component,
30274
31689
  args: [{ selector: 'app-mobile-handbook-page', standalone: true, imports: [DsMobilePageMainComponent, DsMobileSectionComponent, DsMobileHandbookFolderComponent, DsMobileOfflineBannerComponent], template: `
30275
- <ds-mobile-page-main #pageComponent title="Håndbog" [avatarInitials]="userService.avatarInitials()" [avatarType]="userService.avatarType()" (refresh)="handleRefresh($event)">
31690
+ <ds-mobile-page-main #pageComponent title="Håndbog" [notificationCount]="notificationService.unreadCount()" [avatarInitials]="userService.avatarInitials()" [avatarType]="userService.avatarType()" (notificationClick)="handleNotificationClick()" (refresh)="handleRefresh($event)">
30276
31691
  <!-- Offline indicator -->
30277
31692
  @if (pageComponent.isOffline()) {
30278
31693
  <ds-mobile-offline-banner offline-indicator title="Ingen internetforbindelse" message="Nogle funktioner kan være utilgængelige"> </ds-mobile-offline-banner>
@@ -30864,6 +32279,8 @@ class MobileHomePageComponent {
30864
32279
  modalCtrl = inject(ModalController);
30865
32280
  vendorModal = inject(DsMobileServiceVendorModalService);
30866
32281
  newInquiryModal = inject(DsMobileNewInquiryModalService);
32282
+ notificationModal = inject(DsMobileNotificationModalService);
32283
+ notificationService = inject(NotificationService);
30867
32284
  constructor(router, navCtrl, userService, postsService, postModal, trackingPermissionService, bottomSheet, familyAccessService, peerMessaging, peerChat, lightboxService) {
30868
32285
  this.router = router;
30869
32286
  this.navCtrl = navCtrl;
@@ -30891,6 +32308,11 @@ class MobileHomePageComponent {
30891
32308
  }, delayMs);
30892
32309
  });
30893
32310
  }
32311
+ async handleNotificationClick() {
32312
+ const tapped = await this.notificationModal.open();
32313
+ if (tapped)
32314
+ console.log('Notification tapped:', tapped.type, tapped.id);
32315
+ }
30894
32316
  handleRefresh(event) {
30895
32317
  console.log('Pull-to-refresh triggered');
30896
32318
  setTimeout(() => {
@@ -31035,8 +32457,10 @@ class MobileHomePageComponent {
31035
32457
  #pageComponent
31036
32458
  title="Hjem"
31037
32459
  headerTitle="Velkommen, Lars"
32460
+ [notificationCount]="notificationService.unreadCount()"
31038
32461
  [avatarInitials]="userService.avatarInitials()"
31039
32462
  [avatarType]="userService.avatarType()"
32463
+ (notificationClick)="handleNotificationClick()"
31040
32464
  (refresh)="handleRefresh($event)"
31041
32465
  >
31042
32466
 
@@ -31239,7 +32663,7 @@ class MobileHomePageComponent {
31239
32663
  </ds-mobile-page-main>
31240
32664
  } <!-- end @if (!isCoveringScreen()) -->
31241
32665
 
31242
- `, isInline: true, styles: [".posts-list,.messages-preview-list{display:flex;flex-direction:column}.property-banner-nav{display:block;width:100%;border-radius:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.property-banner-nav:focus-visible{outline:2px solid var(--color-accent, #6B5FF5);outline-offset:2px}.inquiries-list,.services-preview-list{display:flex;flex-direction:column}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;text-align:center}.empty-state ds-button{display:block;margin-top:16px}.empty-state ds-button::ng-deep .btn{width:100%;border-radius:9999px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}@keyframes slideDown{0%{transform:translateY(-100%);opacity:0}to{transform:translateY(0);opacity:1}}.welcome-toast{padding:10px 14px;border-radius:12px;background:var(--color-background-brand-secondary, #EEF0FF);display:flex;align-items:flex-start;gap:10px;font-size:14px;font-weight:500;color:var(--color-accent, #6B5FF5);animation:slideDown .2s ease-out}.toast-icon{width:20px;height:20px;border-radius:50%;background:var(--color-accent, #6B5FF5);display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff;margin-top:1px}.welcome-toast-content{flex:1;display:flex;flex-direction:column;gap:2px}.welcome-toast-heading{font-family:Brockmann,sans-serif;font-size:14px;font-weight:600;color:var(--color-brand-content, #3B3691);margin:0}.welcome-toast-text{font-family:Brockmann,sans-serif;font-size:13px;line-height:1.4;color:var(--color-brand-content, #3B3691);margin:0;opacity:.8}.welcome-toast-text strong{font-weight:600;opacity:1}.toast-dismiss{margin-left:auto;background:none;border:none;cursor:pointer;flex-shrink:0;color:var(--color-accent, #6B5FF5);display:flex;align-items:center;justify-content:center}.home-content--animating{animation:homeReveal .3s var(--spring-curve-smooth) both}@keyframes homeReveal{0%{opacity:0;transform:translateY(128px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileIllustrationComponent, selector: "ds-mobile-illustration", inputs: ["variant", "size", "alt"] }, { kind: "component", type: DsMobilePropertyBannerComponent, selector: "ds-mobile-property-banner", inputs: ["address", "photoUrl", "tenantCount"] }, { kind: "component", type: DsMobileInteractiveListItemPostComponent, selector: "ds-mobile-interactive-list-item-post", inputs: ["authorName", "authorRole", "timestamp", "avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "showBadge", "variant", "align", "clickable", "enableLongPress", "moreActions"], outputs: ["postClick", "commentClick", "longPress"] }, { kind: "component", type: DsMobileInteractiveListItemInquiryComponent, selector: "ds-mobile-interactive-list-item-inquiry", inputs: ["title", "description", "status", "statusLabel", "timestamp", "iconName", "iconColor", "variant", "align", "clickable", "showChevron", "enableLongPress", "moreActions"], outputs: ["inquiryClick", "longPress"] }, { kind: "component", type: DsMobileInteractiveListItemMessageComponent, selector: "ds-mobile-interactive-list-item-message", inputs: ["senderName", "senderRole", "timestamp", "message", "avatarInitials", "avatarType", "avatarSrc", "unread", "unreadStyle", "clickable", "align", "showAvatarBadge", "groupStackMembers", "groupCustomAvatarUrl", "groupStackExcludeParticipantId"], outputs: ["messageClick", "longPress"] }, { kind: "component", type: DsMobileInteractiveListItemServiceComponent, selector: "ds-mobile-interactive-list-item-service", inputs: ["title", "description", "logo", "showChevron"], outputs: ["serviceClick"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: PostContentComponent, selector: "post-content" }, { kind: "component", type: PostTextComponent, selector: "post-text" }, { kind: "component", type: PostActionsComponent, selector: "post-actions" }, { kind: "component", type: ActionLikeComponent, selector: "action-like", inputs: ["active", "count"], outputs: ["activeChange", "countChange", "likeClick"] }, { kind: "component", type: ActionCommentComponent, selector: "action-comment", inputs: ["count"], outputs: ["commentClick"] }] });
32666
+ `, isInline: true, styles: [".posts-list,.messages-preview-list{display:flex;flex-direction:column}.property-banner-nav{display:block;width:100%;border-radius:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.property-banner-nav:focus-visible{outline:2px solid var(--color-accent, #6B5FF5);outline-offset:2px}.inquiries-list,.services-preview-list{display:flex;flex-direction:column}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;text-align:center}.empty-state ds-button{display:block;margin-top:16px}.empty-state ds-button::ng-deep .btn{width:100%;border-radius:9999px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}@keyframes slideDown{0%{transform:translateY(-100%);opacity:0}to{transform:translateY(0);opacity:1}}.welcome-toast{padding:10px 14px;border-radius:12px;background:var(--color-background-brand-secondary, #EEF0FF);display:flex;align-items:flex-start;gap:10px;font-size:14px;font-weight:500;color:var(--color-accent, #6B5FF5);animation:slideDown .2s ease-out}.toast-icon{width:20px;height:20px;border-radius:50%;background:var(--color-accent, #6B5FF5);display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff;margin-top:1px}.welcome-toast-content{flex:1;display:flex;flex-direction:column;gap:2px}.welcome-toast-heading{font-family:Brockmann,sans-serif;font-size:14px;font-weight:600;color:var(--color-brand-content, #3B3691);margin:0}.welcome-toast-text{font-family:Brockmann,sans-serif;font-size:13px;line-height:1.4;color:var(--color-brand-content, #3B3691);margin:0;opacity:.8}.welcome-toast-text strong{font-weight:600;opacity:1}.toast-dismiss{margin-left:auto;background:none;border:none;cursor:pointer;flex-shrink:0;color:var(--color-accent, #6B5FF5);display:flex;align-items:center;justify-content:center}.home-content--animating{animation:homeReveal .3s var(--spring-curve-smooth) both}@keyframes homeReveal{0%{opacity:0;transform:translateY(128px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileIllustrationComponent, selector: "ds-mobile-illustration", inputs: ["variant", "size", "alt"] }, { kind: "component", type: DsMobilePropertyBannerComponent, selector: "ds-mobile-property-banner", inputs: ["address", "photoUrl", "tenantCount"] }, { kind: "component", type: DsMobileInteractiveListItemPostComponent, selector: "ds-mobile-interactive-list-item-post", inputs: ["authorName", "authorRole", "timestamp", "avatarInitials", "avatarType", "avatarSrc", "avatarIconName", "showBadge", "variant", "align", "clickable", "enableLongPress", "moreActions"], outputs: ["postClick", "commentClick", "longPress"] }, { kind: "component", type: DsMobileInteractiveListItemInquiryComponent, selector: "ds-mobile-interactive-list-item-inquiry", inputs: ["title", "description", "status", "statusLabel", "timestamp", "iconName", "iconColor", "variant", "align", "clickable", "showChevron", "enableLongPress", "moreActions"], outputs: ["inquiryClick", "longPress"] }, { kind: "component", type: DsMobileInteractiveListItemMessageComponent, selector: "ds-mobile-interactive-list-item-message", inputs: ["senderName", "senderRole", "timestamp", "message", "avatarInitials", "avatarType", "avatarSrc", "unread", "unreadStyle", "clickable", "align", "showAvatarBadge", "groupStackMembers", "groupCustomAvatarUrl", "groupStackExcludeParticipantId"], outputs: ["messageClick", "longPress"] }, { kind: "component", type: DsMobileInteractiveListItemServiceComponent, selector: "ds-mobile-interactive-list-item-service", inputs: ["title", "description", "logo", "showChevron"], outputs: ["serviceClick"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: PostContentComponent, selector: "post-content" }, { kind: "component", type: PostTextComponent, selector: "post-text" }, { kind: "component", type: PostActionsComponent, selector: "post-actions" }, { kind: "component", type: ActionLikeComponent, selector: "action-like", inputs: ["active", "count"], outputs: ["activeChange", "countChange", "likeClick"] }, { kind: "component", type: ActionCommentComponent, selector: "action-comment", inputs: ["count"], outputs: ["commentClick"] }] });
31243
32667
  }
31244
32668
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileHomePageComponent, decorators: [{
31245
32669
  type: Component,
@@ -31253,7 +32677,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
31253
32677
  DsMobileInteractiveListItemPostComponent,
31254
32678
  DsMobileInteractiveListItemInquiryComponent,
31255
32679
  DsMobileInteractiveListItemMessageComponent,
31256
- DsMobileInteractiveListItemBookingComponent,
31257
32680
  DsMobileInteractiveListItemServiceComponent,
31258
32681
  DsMobileOfflineBannerComponent,
31259
32682
  PostContentComponent,
@@ -31275,8 +32698,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
31275
32698
  #pageComponent
31276
32699
  title="Hjem"
31277
32700
  headerTitle="Velkommen, Lars"
32701
+ [notificationCount]="notificationService.unreadCount()"
31278
32702
  [avatarInitials]="userService.avatarInitials()"
31279
32703
  [avatarType]="userService.avatarType()"
32704
+ (notificationClick)="handleNotificationClick()"
31280
32705
  (refresh)="handleRefresh($event)"
31281
32706
  >
31282
32707
 
@@ -31491,6 +32916,8 @@ class MobileInquiriesPageComponent {
31491
32916
  newInquiryModal;
31492
32917
  pageComponent;
31493
32918
  inquiriesService = inject(InquiriesService);
32919
+ notificationModal = inject(DsMobileNotificationModalService);
32920
+ notificationService = inject(NotificationService);
31494
32921
  constructor(userService, navCtrl, newInquiryModal) {
31495
32922
  this.userService = userService;
31496
32923
  this.navCtrl = navCtrl;
@@ -31526,6 +32953,11 @@ class MobileInquiriesPageComponent {
31526
32953
  console.log('Showing actions for inquiry:', inquiryId);
31527
32954
  // Show bottom sheet with actions (edit, delete, etc.)
31528
32955
  }
32956
+ async handleNotificationClick() {
32957
+ const tapped = await this.notificationModal.open();
32958
+ if (tapped)
32959
+ console.log('Notification tapped:', tapped.type, tapped.id);
32960
+ }
31529
32961
  handleRefresh(event) {
31530
32962
  console.log('Pull-to-refresh triggered on inquiries page');
31531
32963
  // Check if offline and complete immediately
@@ -31565,8 +32997,10 @@ class MobileInquiriesPageComponent {
31565
32997
  <ds-mobile-page-main
31566
32998
  #pageComponent
31567
32999
  title="Henvendelser"
33000
+ [notificationCount]="notificationService.unreadCount()"
31568
33001
  [avatarInitials]="userService.avatarInitials()"
31569
33002
  [avatarType]="userService.avatarType()"
33003
+ (notificationClick)="handleNotificationClick()"
31570
33004
  (refresh)="handleRefresh($event)">
31571
33005
 
31572
33006
  <!-- Offline indicator -->
@@ -31630,7 +33064,7 @@ class MobileInquiriesPageComponent {
31630
33064
  ariaLabel="Create new inquiry"
31631
33065
  (clicked)="createNewInquiry()">
31632
33066
  </ds-mobile-fab>
31633
- `, isInline: true, styles: [".inquiry-list-wrapper{display:flex;flex-direction:column;margin-top:-12px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileIllustrationComponent, selector: "ds-mobile-illustration", inputs: ["variant", "size", "alt"] }, { kind: "component", type: DsMobileInteractiveListItemInquiryComponent, selector: "ds-mobile-interactive-list-item-inquiry", inputs: ["title", "description", "status", "statusLabel", "timestamp", "iconName", "iconColor", "variant", "align", "clickable", "showChevron", "enableLongPress", "moreActions"], outputs: ["inquiryClick", "longPress"] }, { kind: "component", type: DsMobileInlineTabsComponent, selector: "ds-mobile-inline-tabs", inputs: ["tabs", "activeTab"], outputs: ["tabChange"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: DsMobileFabComponent, selector: "ds-mobile-fab", inputs: ["icon", "position", "size", "ariaLabel", "disabled"], outputs: ["clicked"] }] });
33067
+ `, isInline: true, styles: [".inquiry-list-wrapper{display:flex;flex-direction:column;margin-top:-12px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileIllustrationComponent, selector: "ds-mobile-illustration", inputs: ["variant", "size", "alt"] }, { kind: "component", type: DsMobileInteractiveListItemInquiryComponent, selector: "ds-mobile-interactive-list-item-inquiry", inputs: ["title", "description", "status", "statusLabel", "timestamp", "iconName", "iconColor", "variant", "align", "clickable", "showChevron", "enableLongPress", "moreActions"], outputs: ["inquiryClick", "longPress"] }, { kind: "component", type: DsMobileInlineTabsComponent, selector: "ds-mobile-inline-tabs", inputs: ["tabs", "activeTab"], outputs: ["tabChange"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: DsMobileFabComponent, selector: "ds-mobile-fab", inputs: ["icon", "position", "size", "ariaLabel", "disabled"], outputs: ["clicked"] }] });
31634
33068
  }
31635
33069
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileInquiriesPageComponent, decorators: [{
31636
33070
  type: Component,
@@ -31648,8 +33082,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
31648
33082
  <ds-mobile-page-main
31649
33083
  #pageComponent
31650
33084
  title="Henvendelser"
33085
+ [notificationCount]="notificationService.unreadCount()"
31651
33086
  [avatarInitials]="userService.avatarInitials()"
31652
33087
  [avatarType]="userService.avatarType()"
33088
+ (notificationClick)="handleNotificationClick()"
31653
33089
  (refresh)="handleRefresh($event)">
31654
33090
 
31655
33091
  <!-- Offline indicator -->
@@ -32321,6 +33757,87 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
32321
33757
  `, styles: [".activity-list{display:flex;flex-direction:column;gap:12px;position:relative}.activity-list ds-mobile-list-item:not(:last-child) [content-leading]:after{content:\"\";position:absolute;top:40px;left:50%;transform:translate(-50%);width:1px;height:calc(100% - 2px);background:var(--border-color-default, #e5e5e5);z-index:0}.activity-icon-wrapper{width:100%;height:100%;border-radius:8px;background:var(--color-background-neutral-secondary, #f5f5f5);display:flex;align-items:center;justify-content:center;flex-shrink:0;position:relative;z-index:1}.avatar-wrapper{position:relative;display:flex;align-items:start;justify-content:center;flex-shrink:0;width:100%;height:100%;z-index:1}.avatar-badge{position:absolute;bottom:-6px;right:-6px;width:20px;height:20px;border-radius:8px;background:var(--color-brand-secondary, #5d5fef);display:flex;align-items:center;justify-content:center;border:2px solid var(--color-background-primary, #ffffff)}.avatar-badge svg{width:10px;position:relative;top:1px;fill:#fff}.activity-content{display:flex;flex-direction:column;gap:4px}.activity-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227);margin:0}.activity-title .actor-name{font-weight:600;color:var(--text-color-default-primary, #202227)}.activity-title .activity-text{color:var(--text-color-default-secondary, #545B66);font-weight:400}.activity-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:20px;letter-spacing:-.3px;color:var(--text-color-default-secondary, #545B66);margin:0}.activity-timestamp{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:var(--text-color-default-tertiary, #737373);display:flex;align-items:center;gap:4px;margin-top:2px}.detail-label{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:var(--text-color-default-tertiary, #737373)}.detail-value{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:24px;letter-spacing:-.3px;color:var(--text-color-default-primary, #202227)}.detail-tag{display:inline-flex;align-items:center;padding:4px 12px;border-radius:12px;background:var(--color-background-neutral-secondary, #f5f5f5);font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:var(--text-color-default-secondary, #525866);margin-top:4px;margin-bottom:10px;width:-moz-fit-content;width:fit-content}.photo-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:8px}.photo-add{width:100%;aspect-ratio:1;border-radius:12px;border:1px dashed var(--border-color-default, #e5e5e5);background:var(--color-background-neutral-secondary, #f5f5f5);display:flex;align-items:center;justify-content:center;cursor:pointer}.photo-item{width:100%;aspect-ratio:1;border-radius:12px;-o-object-fit:cover;object-fit:cover}.empty-messages{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;gap:12px}.empty-messages-image{width:96px;height:96px;-o-object-fit:contain;object-fit:contain}.empty-messages-text{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);color:var(--text-color-default-secondary, #71727a);margin:0}\n"] }]
32322
33758
  }], ctorParameters: () => [{ type: UserService }, { type: DsMobileLightboxService }, { type: DsMobileChatModalService }] });
32323
33759
 
33760
+ const STORAGE_KEY = 'ds-notification-prompt-dismissed';
33761
+ const COOLDOWN_MS = 24 * 60 * 60 * 1000; // 24 hours
33762
+ /**
33763
+ * Manages the notification permission prompt lifecycle.
33764
+ *
33765
+ * - Stores the user's "Not now" dismissal timestamp in localStorage
33766
+ * - Re-shows the prompt after a 24-hour cooldown
33767
+ * - Skips the prompt entirely if native permissions are already granted
33768
+ *
33769
+ * Downstream integration: call `checkPermissions()` before checking
33770
+ * `shouldShowPrompt()` to incorporate native permission status.
33771
+ */
33772
+ class NotificationPromptService {
33773
+ permissionGranted = false;
33774
+ /**
33775
+ * Record that the user dismissed the prompt. Stores the current
33776
+ * timestamp so the prompt can be suppressed for 24 hours.
33777
+ */
33778
+ dismiss() {
33779
+ try {
33780
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({ dismissedAt: Date.now() }));
33781
+ }
33782
+ catch {
33783
+ // localStorage unavailable (SSR, private browsing quota)
33784
+ }
33785
+ }
33786
+ /**
33787
+ * Whether the prompt should be displayed right now.
33788
+ * Returns false if:
33789
+ * - Native permission is already granted
33790
+ * - The user dismissed less than 24 hours ago
33791
+ */
33792
+ shouldShowPrompt() {
33793
+ if (this.permissionGranted)
33794
+ return false;
33795
+ try {
33796
+ const raw = localStorage.getItem(STORAGE_KEY);
33797
+ if (!raw)
33798
+ return true;
33799
+ const { dismissedAt } = JSON.parse(raw);
33800
+ return Date.now() - dismissedAt >= COOLDOWN_MS;
33801
+ }
33802
+ catch {
33803
+ return true;
33804
+ }
33805
+ }
33806
+ /**
33807
+ * Call this on app launch to check native notification permission status.
33808
+ * If already granted, the prompt will never be shown.
33809
+ */
33810
+ async checkPermissions() {
33811
+ try {
33812
+ const { PushNotifications } = await import('@capacitor/push-notifications');
33813
+ const result = await PushNotifications.checkPermissions();
33814
+ this.permissionGranted = result.receive === 'granted';
33815
+ }
33816
+ catch {
33817
+ // Not on native — leave as false so the prompt can still show
33818
+ }
33819
+ }
33820
+ /**
33821
+ * Mark that the user has granted permission (called after a successful
33822
+ * allow flow so the prompt is permanently hidden for this session).
33823
+ */
33824
+ markGranted() {
33825
+ this.permissionGranted = true;
33826
+ try {
33827
+ localStorage.removeItem(STORAGE_KEY);
33828
+ }
33829
+ catch {
33830
+ // noop
33831
+ }
33832
+ }
33833
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationPromptService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
33834
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationPromptService, providedIn: 'root' });
33835
+ }
33836
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationPromptService, decorators: [{
33837
+ type: Injectable,
33838
+ args: [{ providedIn: 'root' }]
33839
+ }] });
33840
+
32324
33841
  /**
32325
33842
  * Whitelabel Demo Modal Component
32326
33843
  *
@@ -33460,7 +34977,11 @@ class MobileTabsExampleComponent {
33460
34977
  navCtrl;
33461
34978
  whitelabelDemoModal = inject(WhitelabelDemoModalService);
33462
34979
  trackingPermissionService = inject(TrackingPermissionService);
34980
+ notificationModal = inject(DsMobileNotificationModalService);
34981
+ notificationService = inject(NotificationService);
34982
+ notificationPrompt = inject(NotificationPromptService);
33463
34983
  pageLoading = inject(PageLoadingService);
34984
+ showNotificationPrompt = signal(false, ...(ngDevMode ? [{ debugName: "showNotificationPrompt" }] : []));
33464
34985
  trackedProfileMenuItems = computed(() => {
33465
34986
  const accountActions = [
33466
34987
  {
@@ -33540,15 +35061,15 @@ class MobileTabsExampleComponent {
33540
35061
  this.userService.setProfileMenuItems(this.trackedProfileMenuItems());
33541
35062
  });
33542
35063
  }
33543
- ngOnInit() {
35064
+ async ngOnInit() {
33544
35065
  console.log('MobileTabsExampleComponent ngOnInit');
33545
- // Configure user avatar globally - this is now the single source of truth
33546
35066
  this.userService.setDisplayName('Lucas Møller');
33547
35067
  this.userService.setAddress('Toftegårds Allé 5A, 3. tv.');
33548
35068
  this.userService.setAvatarInitials('LM');
33549
35069
  this.userService.setAvatarType('initials');
33550
- // Initial status refresh ensures menu reflects past ATT choice.
33551
35070
  void this.trackingPermissionService.refreshTrackingStatus();
35071
+ await this.notificationPrompt.checkPermissions();
35072
+ this.showNotificationPrompt.set(this.notificationPrompt.shouldShowPrompt());
33552
35073
  }
33553
35074
  tabs = [
33554
35075
  {
@@ -33655,6 +35176,34 @@ class MobileTabsExampleComponent {
33655
35176
  });
33656
35177
  }
33657
35178
  }
35179
+ async handleNotificationAllow() {
35180
+ console.log('Notification permission: requesting native dialog...');
35181
+ this.showNotificationPrompt.set(false);
35182
+ try {
35183
+ const { PushNotifications } = await import('@capacitor/push-notifications');
35184
+ const permission = await PushNotifications.requestPermissions();
35185
+ console.log('Push permission result:', permission.receive);
35186
+ if (permission.receive === 'granted') {
35187
+ await PushNotifications.register();
35188
+ this.notificationPrompt.markGranted();
35189
+ console.log('Push notifications registered');
35190
+ }
35191
+ }
35192
+ catch (err) {
35193
+ console.warn('Push notifications not available (web/simulator):', err);
35194
+ }
35195
+ }
35196
+ handleNotificationDismiss() {
35197
+ console.log('Notification permission: dismissed (24hr cooldown)');
35198
+ this.notificationPrompt.dismiss();
35199
+ this.showNotificationPrompt.set(false);
35200
+ }
35201
+ async handleNotificationClick() {
35202
+ const tapped = await this.notificationModal.open();
35203
+ if (tapped) {
35204
+ console.log('Notification tapped:', tapped.type, tapped.id);
35205
+ }
35206
+ }
33658
35207
  /** Called by the error overlay retry button — clears error and re-navigates to home */
33659
35208
  handleRetry() {
33660
35209
  this.pageLoading.hasError.set(false);
@@ -33676,26 +35225,35 @@ class MobileTabsExampleComponent {
33676
35225
  </div>
33677
35226
  }
33678
35227
 
35228
+ <!-- Notification permission prompt (session-only, reappears on next launch) -->
35229
+ @if (showNotificationPrompt() && !pageLoading.isCoveringScreen()) {
35230
+ <ds-mobile-notification-prompt
35231
+ (allow)="handleNotificationAllow()"
35232
+ (dismiss)="handleNotificationDismiss()" />
35233
+ }
35234
+
33679
35235
  <ion-tabs class="ds-tabs-wrapper">
33680
35236
  <!-- Tab bar is hidden during the loading state -->
33681
35237
  @if (!pageLoading.isCoveringScreen()) {
33682
35238
  <ds-mobile-tab-bar
33683
35239
  [tabs]="tabs"
33684
35240
  [moreMenuItems]="moreMenuItems"
35241
+ [notificationCount]="notificationService.unreadCount()"
33685
35242
  [avatarInitials]="userService.avatarInitials()"
33686
35243
  [avatarType]="userService.avatarType()"
33687
35244
  [profileMenuItems]="profileMenuItems"
35245
+ (notificationClick)="handleNotificationClick()"
33688
35246
  (profileActionSelected)="handleProfileAction($event)"
33689
35247
  (moreMenuItemSelected)="handleMoreMenuAction($event)"
33690
35248
  >
33691
35249
  </ds-mobile-tab-bar>
33692
35250
  }
33693
35251
  </ion-tabs>
33694
- `, isInline: true, styles: [":host{display:block;height:100dvh;width:100vw;position:relative}.app-error{position:fixed;inset:0;z-index:9999;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:40px 32px;background:var(--color-header-surface, #2D2356)}.app-error__title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--color-header-content, #fff);text-align:center;margin:0}.app-error__description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:#ffffffa6;text-align:center;margin:0 0 8px}.app-error ds-button::ng-deep .btn{border-radius:9999px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonTabs, selector: "ion-tabs" }, { kind: "component", type: DsMobileTabBarComponent, selector: "ds-mobile-tab-bar", inputs: ["tabs", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "profileMenuItems", "moreMenuItems", "moreMenuLabel", "moreMenuIcon", "moreMenuIconActive"], outputs: ["avatarClick", "profileActionSelected", "moreMenuItemSelected"] }, { kind: "component", type: DsMobileAppLoadingComponent, selector: "ds-mobile-app-loading", inputs: ["isExiting"], outputs: ["exitComplete"] }, { kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }] });
35252
+ `, isInline: true, styles: [":host{display:block;height:100dvh;width:100vw;position:relative}.app-error{position:fixed;inset:0;z-index:9999;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:40px 32px;background:var(--color-header-surface, #2D2356)}.app-error__title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--color-header-content, #fff);text-align:center;margin:0}.app-error__description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:#ffffffa6;text-align:center;margin:0 0 8px}.app-error ds-button::ng-deep .btn{border-radius:9999px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonTabs, selector: "ion-tabs" }, { kind: "component", type: DsMobileTabBarComponent, selector: "ds-mobile-tab-bar", inputs: ["tabs", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "profileMenuItems", "notificationCount", "moreMenuItems", "moreMenuLabel", "moreMenuIcon", "moreMenuIconActive"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "moreMenuItemSelected"] }, { kind: "component", type: DsMobileAppLoadingComponent, selector: "ds-mobile-app-loading", inputs: ["isExiting"], outputs: ["exitComplete"] }, { kind: "component", type: DsMobileNotificationPromptComponent, selector: "ds-mobile-notification-prompt", inputs: ["heading", "subtitle", "allowLabel", "dismissLabel"], outputs: ["allow", "dismiss"] }, { kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }] });
33695
35253
  }
33696
35254
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileTabsExampleComponent, decorators: [{
33697
35255
  type: Component,
33698
- args: [{ selector: 'app-mobile-tabs-example', standalone: true, imports: [CommonModule, IonTabs, DsMobileTabBarComponent, DsMobileAppLoadingComponent, DsButtonComponent], template: `
35256
+ args: [{ selector: 'app-mobile-tabs-example', standalone: true, imports: [CommonModule, IonTabs, DsMobileTabBarComponent, DsMobileAppLoadingComponent, DsMobileNotificationPromptComponent, DsButtonComponent], template: `
33699
35257
  <!-- Full-screen loading screen rendered at layout level so it covers the tab bar.
33700
35258
  Kept in DOM during exiting so the exit animation can play. -->
33701
35259
  @if (pageLoading.isLoading() || pageLoading.isExiting()) {
@@ -33710,15 +35268,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
33710
35268
  </div>
33711
35269
  }
33712
35270
 
35271
+ <!-- Notification permission prompt (session-only, reappears on next launch) -->
35272
+ @if (showNotificationPrompt() && !pageLoading.isCoveringScreen()) {
35273
+ <ds-mobile-notification-prompt
35274
+ (allow)="handleNotificationAllow()"
35275
+ (dismiss)="handleNotificationDismiss()" />
35276
+ }
35277
+
33713
35278
  <ion-tabs class="ds-tabs-wrapper">
33714
35279
  <!-- Tab bar is hidden during the loading state -->
33715
35280
  @if (!pageLoading.isCoveringScreen()) {
33716
35281
  <ds-mobile-tab-bar
33717
35282
  [tabs]="tabs"
33718
35283
  [moreMenuItems]="moreMenuItems"
35284
+ [notificationCount]="notificationService.unreadCount()"
33719
35285
  [avatarInitials]="userService.avatarInitials()"
33720
35286
  [avatarType]="userService.avatarType()"
33721
35287
  [profileMenuItems]="profileMenuItems"
35288
+ (notificationClick)="handleNotificationClick()"
33722
35289
  (profileActionSelected)="handleProfileAction($event)"
33723
35290
  (moreMenuItemSelected)="handleMoreMenuAction($event)"
33724
35291
  >
@@ -33728,393 +35295,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
33728
35295
  `, styles: [":host{display:block;height:100dvh;width:100vw;position:relative}.app-error{position:fixed;inset:0;z-index:9999;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:40px 32px;background:var(--color-header-surface, #2D2356)}.app-error__title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--color-header-content, #fff);text-align:center;margin:0}.app-error__description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:#ffffffa6;text-align:center;margin:0 0 8px}.app-error ds-button::ng-deep .btn{border-radius:9999px}\n"] }]
33729
35296
  }], ctorParameters: () => [{ type: UserService }, { type: i1$3.Router }, { type: i1.NavController }] });
33730
35297
 
33731
- class DsMobileBookingDetailSheetComponent {
33732
- modalController;
33733
- /** `sheet` = bottom sheet. `modal` = `ds-modal-base` shell (active + history bookings). */
33734
- presentation = 'sheet';
33735
- data;
33736
- /** When true the modal sizes to its content instead of filling the screen. */
33737
- autoHeight = false;
33738
- get isModalPresentation() {
33739
- return this.presentation === 'modal';
33740
- }
33741
- constructor(modalController) {
33742
- this.modalController = modalController;
33743
- }
33744
- close() {
33745
- this.modalController.dismiss(null, 'backdrop');
33746
- }
33747
- cancelBooking() {
33748
- this.modalController.dismiss(null, 'cancel');
33749
- }
33750
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetComponent, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Component });
33751
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileBookingDetailSheetComponent, isStandalone: true, selector: "ds-mobile-booking-detail-sheet", inputs: { presentation: "presentation", data: "data", autoHeight: "autoHeight" }, host: { properties: { "class.presentation-modal": "this.isModalPresentation" } }, ngImport: i0, template: `
33752
- @if (presentation === 'modal') {
33753
- <ds-mobile-modal-base
33754
- [headerTitle]="data.facilityTitle"
33755
- [closeButtonLabel]="'Luk'"
33756
- [isAutoHeight]="autoHeight"
33757
- [keyboardContentBehavior]="'follow'">
33758
-
33759
- <ds-mobile-section
33760
- [showBorder]="false"
33761
- padding="20px 20px 0 20px">
33762
- <div class="hero-image-container">
33763
- @if (data.heroImage) {
33764
- <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
33765
- } @else {
33766
- <div class="hero-image-placeholder">
33767
- <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
33768
- </div>
33769
- }
33770
- </div>
33771
- </ds-mobile-section>
33772
-
33773
- <ds-mobile-section padding="20px 20px 20px 20px">
33774
- <div class="booking-summary-card">
33775
- <div class="details-section">
33776
- <h3 class="details-heading">Booking detaljer</h3>
33777
- <div class="info-rows">
33778
- @if (data.bookingDate) {
33779
- <div class="info-row">
33780
- <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
33781
- <span>{{ data.bookingDate }}</span>
33782
- </div>
33783
- }
33784
- @if (data.bookingTime) {
33785
- <div class="info-row">
33786
- <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
33787
- <span>{{ data.bookingTime }}</span>
33788
- </div>
33789
- }
33790
- @if (data.price) {
33791
- <div class="info-row">
33792
- <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
33793
- <span>{{ data.price }}</span>
33794
- </div>
33795
- }
33796
- @if (data.bookingType) {
33797
- <div class="info-row">
33798
- <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
33799
- <span>{{ data.bookingType }}</span>
33800
- </div>
33801
- }
33802
- @for (req of data.requirements || []; track req) {
33803
- <div class="info-row">
33804
- <ds-icon name="remixLockLine" size="16px" color="tertiary" />
33805
- <span>{{ req }}</span>
33806
- </div>
33807
- }
33808
- </div>
33809
- </div>
33810
- </div>
33811
- </ds-mobile-section>
33812
-
33813
- @if (data.canCancel) {
33814
- <div class="booking-action">
33815
- <div class="booking-actions-row">
33816
- <ds-button
33817
- class="cancel-primary"
33818
- size="md"
33819
- variant="secondary"
33820
- (clicked)="cancelBooking()">
33821
- Annuller booking
33822
- </ds-button>
33823
- </div>
33824
- </div>
33825
- }
33826
- </ds-mobile-modal-base>
33827
- } @else {
33828
- <ds-mobile-bottom-sheet-wrapper [showDragHandle]="true">
33829
- <div class="detail-header">
33830
- <h2 class="detail-title">{{ data.facilityTitle }}</h2>
33831
- <ds-icon-button
33832
- icon="remixCloseLine"
33833
- variant="ghost"
33834
- size="sm"
33835
- (clicked)="close()"
33836
- aria-label="Luk"
33837
- />
33838
- </div>
33839
-
33840
- <div class="detail-content">
33841
- <div class="hero-image-container">
33842
- @if (data.heroImage) {
33843
- <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
33844
- } @else {
33845
- <div class="hero-image-placeholder">
33846
- <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
33847
- </div>
33848
- }
33849
- </div>
33850
-
33851
- <div class="booking-summary-card">
33852
- <div class="details-section">
33853
- <h3 class="details-heading">Booking detaljer</h3>
33854
- <div class="info-rows">
33855
- @if (data.bookingDate) {
33856
- <div class="info-row">
33857
- <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
33858
- <span>{{ data.bookingDate }}</span>
33859
- </div>
33860
- }
33861
- @if (data.bookingTime) {
33862
- <div class="info-row">
33863
- <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
33864
- <span>{{ data.bookingTime }}</span>
33865
- </div>
33866
- }
33867
- @if (data.price) {
33868
- <div class="info-row">
33869
- <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
33870
- <span>{{ data.price }}</span>
33871
- </div>
33872
- }
33873
- @if (data.bookingType) {
33874
- <div class="info-row">
33875
- <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
33876
- <span>{{ data.bookingType }}</span>
33877
- </div>
33878
- }
33879
- @for (req of data.requirements || []; track req) {
33880
- <div class="info-row">
33881
- <ds-icon name="remixLockLine" size="16px" color="tertiary" />
33882
- <span>{{ req }}</span>
33883
- </div>
33884
- }
33885
- </div>
33886
- </div>
33887
- </div>
33888
-
33889
- @if (data.canCancel) {
33890
- <div class="footer-button-container">
33891
- <div class="booking-actions-column">
33892
- <ds-button
33893
- size="md"
33894
- variant="secondary"
33895
- (clicked)="cancelBooking()">
33896
- Annuller booking
33897
- </ds-button>
33898
- </div>
33899
- </div>
33900
- }
33901
- </div>
33902
- </ds-mobile-bottom-sheet-wrapper>
33903
- }
33904
- `, isInline: true, styles: [":host{display:block;height:auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;box-sizing:border-box}.detail-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 8px}.detail-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:12px}.detail-content{padding:8px 20px 24px;display:flex;flex-direction:column;gap:20px}.hero-image-container{width:100%;aspect-ratio:16 / 9;border-radius:12px;overflow:hidden;background:var(--color-surface-secondary, #f5f5f5)}.hero-image{width:100%;height:100%;object-fit:cover;display:block}.hero-image-placeholder{width:100%;height:100%;min-height:0;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,color-mix(in srgb,var(--color-accent, #6B5FF5) 30%,transparent),color-mix(in srgb,var(--color-accent, #6B5FF5) 60%,transparent))}.booking-summary-card{background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:16px;padding:16px}.details-section{display:flex;flex-direction:column;gap:12px}.details-heading{font-family:Brockmann,sans-serif;font-size:var(--font-size-base, 16px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0}.info-rows{display:flex;flex-direction:column;gap:8px}.info-row{display:flex;align-items:center;gap:10px;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);color:var(--text-color-default-secondary, #545B66);line-height:1.4}.info-row ds-icon{flex-shrink:0}.footer-button-container{width:100%;padding:16px 20px 0}.footer-button-container ::ng-deep ds-button{display:block;width:100%}.footer-button-container ::ng-deep ds-button button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px}.booking-action{padding:16px 20px;padding-bottom:calc(16px + max(8px,env(safe-area-inset-bottom,0px) - 24px));background:var(--color-surface-primary, #ffffff)}.booking-actions-row{display:flex;flex-direction:row;align-items:center;gap:12px;width:100%}.booking-action .cancel-primary{flex:1;min-width:0;display:block}.booking-action .cancel-primary ::ng-deep button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px;padding-left:16px;padding-right:16px}:host.presentation-modal{display:block;height:auto;width:100%;min-height:0;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsMobileBottomSheetWrapperComponent, selector: "ds-mobile-bottom-sheet-wrapper", inputs: ["showDragHandle"] }, { kind: "component", type: DsMobileModalBaseComponent, selector: "ds-mobile-modal-base", inputs: ["headerTitleInteractive", "showHeader"], outputs: ["titleClick"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }] });
33905
- }
33906
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetComponent, decorators: [{
33907
- type: Component,
33908
- args: [{ selector: 'ds-mobile-booking-detail-sheet', standalone: true, imports: [
33909
- CommonModule,
33910
- DsMobileBottomSheetWrapperComponent,
33911
- DsMobileModalBaseComponent,
33912
- DsMobileSectionComponent,
33913
- DsButtonComponent,
33914
- DsIconComponent,
33915
- DsIconButtonComponent,
33916
- ], template: `
33917
- @if (presentation === 'modal') {
33918
- <ds-mobile-modal-base
33919
- [headerTitle]="data.facilityTitle"
33920
- [closeButtonLabel]="'Luk'"
33921
- [isAutoHeight]="autoHeight"
33922
- [keyboardContentBehavior]="'follow'">
33923
-
33924
- <ds-mobile-section
33925
- [showBorder]="false"
33926
- padding="20px 20px 0 20px">
33927
- <div class="hero-image-container">
33928
- @if (data.heroImage) {
33929
- <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
33930
- } @else {
33931
- <div class="hero-image-placeholder">
33932
- <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
33933
- </div>
33934
- }
33935
- </div>
33936
- </ds-mobile-section>
33937
-
33938
- <ds-mobile-section padding="20px 20px 20px 20px">
33939
- <div class="booking-summary-card">
33940
- <div class="details-section">
33941
- <h3 class="details-heading">Booking detaljer</h3>
33942
- <div class="info-rows">
33943
- @if (data.bookingDate) {
33944
- <div class="info-row">
33945
- <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
33946
- <span>{{ data.bookingDate }}</span>
33947
- </div>
33948
- }
33949
- @if (data.bookingTime) {
33950
- <div class="info-row">
33951
- <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
33952
- <span>{{ data.bookingTime }}</span>
33953
- </div>
33954
- }
33955
- @if (data.price) {
33956
- <div class="info-row">
33957
- <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
33958
- <span>{{ data.price }}</span>
33959
- </div>
33960
- }
33961
- @if (data.bookingType) {
33962
- <div class="info-row">
33963
- <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
33964
- <span>{{ data.bookingType }}</span>
33965
- </div>
33966
- }
33967
- @for (req of data.requirements || []; track req) {
33968
- <div class="info-row">
33969
- <ds-icon name="remixLockLine" size="16px" color="tertiary" />
33970
- <span>{{ req }}</span>
33971
- </div>
33972
- }
33973
- </div>
33974
- </div>
33975
- </div>
33976
- </ds-mobile-section>
33977
-
33978
- @if (data.canCancel) {
33979
- <div class="booking-action">
33980
- <div class="booking-actions-row">
33981
- <ds-button
33982
- class="cancel-primary"
33983
- size="md"
33984
- variant="secondary"
33985
- (clicked)="cancelBooking()">
33986
- Annuller booking
33987
- </ds-button>
33988
- </div>
33989
- </div>
33990
- }
33991
- </ds-mobile-modal-base>
33992
- } @else {
33993
- <ds-mobile-bottom-sheet-wrapper [showDragHandle]="true">
33994
- <div class="detail-header">
33995
- <h2 class="detail-title">{{ data.facilityTitle }}</h2>
33996
- <ds-icon-button
33997
- icon="remixCloseLine"
33998
- variant="ghost"
33999
- size="sm"
34000
- (clicked)="close()"
34001
- aria-label="Luk"
34002
- />
34003
- </div>
34004
-
34005
- <div class="detail-content">
34006
- <div class="hero-image-container">
34007
- @if (data.heroImage) {
34008
- <img [src]="data.heroImage" [alt]="data.facilityTitle" class="hero-image" />
34009
- } @else {
34010
- <div class="hero-image-placeholder">
34011
- <ds-icon name="remixImageLine" size="48px" color="rgba(255,255,255,0.5)" />
34012
- </div>
34013
- }
34014
- </div>
34015
-
34016
- <div class="booking-summary-card">
34017
- <div class="details-section">
34018
- <h3 class="details-heading">Booking detaljer</h3>
34019
- <div class="info-rows">
34020
- @if (data.bookingDate) {
34021
- <div class="info-row">
34022
- <ds-icon name="remixCalendarLine" size="16px" color="tertiary" />
34023
- <span>{{ data.bookingDate }}</span>
34024
- </div>
34025
- }
34026
- @if (data.bookingTime) {
34027
- <div class="info-row">
34028
- <ds-icon name="remixTimeLine" size="16px" color="tertiary" />
34029
- <span>{{ data.bookingTime }}</span>
34030
- </div>
34031
- }
34032
- @if (data.price) {
34033
- <div class="info-row">
34034
- <ds-icon name="remixPriceTag3Line" size="16px" color="tertiary" />
34035
- <span>{{ data.price }}</span>
34036
- </div>
34037
- }
34038
- @if (data.bookingType) {
34039
- <div class="info-row">
34040
- <ds-icon name="remixCheckboxCircleLine" size="16px" color="tertiary" />
34041
- <span>{{ data.bookingType }}</span>
34042
- </div>
34043
- }
34044
- @for (req of data.requirements || []; track req) {
34045
- <div class="info-row">
34046
- <ds-icon name="remixLockLine" size="16px" color="tertiary" />
34047
- <span>{{ req }}</span>
34048
- </div>
34049
- }
34050
- </div>
34051
- </div>
34052
- </div>
34053
-
34054
- @if (data.canCancel) {
34055
- <div class="footer-button-container">
34056
- <div class="booking-actions-column">
34057
- <ds-button
34058
- size="md"
34059
- variant="secondary"
34060
- (clicked)="cancelBooking()">
34061
- Annuller booking
34062
- </ds-button>
34063
- </div>
34064
- </div>
34065
- }
34066
- </div>
34067
- </ds-mobile-bottom-sheet-wrapper>
34068
- }
34069
- `, styles: [":host{display:block;height:auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;box-sizing:border-box}.detail-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 8px}.detail-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-lg, 18px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:12px}.detail-content{padding:8px 20px 24px;display:flex;flex-direction:column;gap:20px}.hero-image-container{width:100%;aspect-ratio:16 / 9;border-radius:12px;overflow:hidden;background:var(--color-surface-secondary, #f5f5f5)}.hero-image{width:100%;height:100%;object-fit:cover;display:block}.hero-image-placeholder{width:100%;height:100%;min-height:0;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,color-mix(in srgb,var(--color-accent, #6B5FF5) 30%,transparent),color-mix(in srgb,var(--color-accent, #6B5FF5) 60%,transparent))}.booking-summary-card{background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:16px;padding:16px}.details-section{display:flex;flex-direction:column;gap:12px}.details-heading{font-family:Brockmann,sans-serif;font-size:var(--font-size-base, 16px);font-weight:600;color:var(--text-color-default-primary, #202227);margin:0}.info-rows{display:flex;flex-direction:column;gap:8px}.info-row{display:flex;align-items:center;gap:10px;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm, 14px);color:var(--text-color-default-secondary, #545B66);line-height:1.4}.info-row ds-icon{flex-shrink:0}.footer-button-container{width:100%;padding:16px 20px 0}.footer-button-container ::ng-deep ds-button{display:block;width:100%}.footer-button-container ::ng-deep ds-button button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px}.booking-action{padding:16px 20px;padding-bottom:calc(16px + max(8px,env(safe-area-inset-bottom,0px) - 24px));background:var(--color-surface-primary, #ffffff)}.booking-actions-row{display:flex;flex-direction:row;align-items:center;gap:12px;width:100%}.booking-action .cancel-primary{flex:1;min-width:0;display:block}.booking-action .cancel-primary ::ng-deep button{width:100%;border-radius:100px;height:44px;min-height:44px;max-height:44px;padding-left:16px;padding-right:16px}:host.presentation-modal{display:block;height:auto;width:100%;min-height:0;overflow:hidden}\n"] }]
34070
- }], ctorParameters: () => [{ type: i1.ModalController }], propDecorators: { presentation: [{
34071
- type: Input
34072
- }], data: [{
34073
- type: Input
34074
- }], autoHeight: [{
34075
- type: Input
34076
- }], isModalPresentation: [{
34077
- type: HostBinding,
34078
- args: ['class.presentation-modal']
34079
- }] } });
34080
-
34081
- class DsMobileBookingDetailSheetService extends BaseModalService {
34082
- bottomSheet;
34083
- constructor(modalController, bottomSheet) {
34084
- super(modalController);
34085
- this.bottomSheet = bottomSheet;
34086
- }
34087
- /**
34088
- * Bottom-sheet presentation (draggable breakpoints). Prefer `openAsModal` for booking lists.
34089
- */
34090
- async open(data) {
34091
- const modal = await this.bottomSheet.create({
34092
- component: DsMobileBookingDetailSheetComponent,
34093
- componentProps: { data },
34094
- autoHeight: true,
34095
- backdropDismiss: true,
34096
- backdropBlur: true,
34097
- });
34098
- const result = await modal.onWillDismiss();
34099
- return { role: result.role, data: result.data ?? undefined };
34100
- }
34101
- /** `ds-modal-base` shell — used for active and past bookings. Auto-heights to content. */
34102
- async openAsModal(data) {
34103
- const modal = await this.createModal(DsMobileBookingDetailSheetComponent, { data, presentation: 'modal', autoHeight: true }, { keyboardClose: true, autoHeight: true });
34104
- await modal.present();
34105
- const result = await modal.onWillDismiss();
34106
- return { role: result.role, data: result.data ?? undefined };
34107
- }
34108
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetService, deps: [{ token: i1.ModalController }, { token: DsMobileBottomSheetService }], target: i0.ɵɵFactoryTarget.Injectable });
34109
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetService, providedIn: 'root' });
34110
- }
34111
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingDetailSheetService, decorators: [{
34112
- type: Injectable,
34113
- args: [{
34114
- providedIn: 'root',
34115
- }]
34116
- }], ctorParameters: () => [{ type: i1.ModalController }, { type: DsMobileBottomSheetService }] });
34117
-
34118
35298
  class DsMobileBookingCancelConfirmationComponent {
34119
35299
  facilityTitle;
34120
35300
  facilityThumbnail;
@@ -34215,6 +35395,8 @@ class MobileBookingPageComponent {
34215
35395
  userService;
34216
35396
  pageComponent;
34217
35397
  bookingsSwiper;
35398
+ notificationModal = inject(DsMobileNotificationModalService);
35399
+ notificationService = inject(NotificationService);
34218
35400
  cancellingBookingId = signal(null, ...(ngDevMode ? [{ debugName: "cancellingBookingId" }] : []));
34219
35401
  lang = signal(resolveLanguage(), ...(ngDevMode ? [{ debugName: "lang" }] : []));
34220
35402
  historyLinkText = computed(() => HISTORY_LINK[this.lang()] ?? HISTORY_LINK['da'], ...(ngDevMode ? [{ debugName: "historyLinkText" }] : []));
@@ -34420,6 +35602,11 @@ class MobileBookingPageComponent {
34420
35602
  }
34421
35603
  ], ...(ngDevMode ? [{ debugName: "availableFacilities" }] : []));
34422
35604
  visibleFacilities = computed(() => this.availableFacilities().filter((f) => !f.archived), ...(ngDevMode ? [{ debugName: "visibleFacilities" }] : []));
35605
+ async handleNotificationClick() {
35606
+ const tapped = await this.notificationModal.open();
35607
+ if (tapped)
35608
+ console.log('Notification tapped:', tapped.type, tapped.id);
35609
+ }
34423
35610
  handleRefresh(event) {
34424
35611
  console.log('Pull-to-refresh triggered');
34425
35612
  // Check if offline and complete immediately
@@ -34710,8 +35897,10 @@ class MobileBookingPageComponent {
34710
35897
  <ds-mobile-page-main
34711
35898
  #pageComponent
34712
35899
  [title]="'Bookinger'"
35900
+ [notificationCount]="notificationService.unreadCount()"
34713
35901
  [avatarInitials]="userService.avatarInitials()"
34714
35902
  [avatarType]="userService.avatarType()"
35903
+ (notificationClick)="handleNotificationClick()"
34715
35904
  (refresh)="handleRefresh($event)">
34716
35905
 
34717
35906
  @if (pageComponent.isOffline()) {
@@ -34816,7 +36005,7 @@ class MobileBookingPageComponent {
34816
36005
  ariaLabel="Opret facilitet"
34817
36006
  (clicked)="openFacilityCreationModal()">
34818
36007
  </ds-mobile-fab>
34819
- `, isInline: true, styles: [".bookings-swiper-wrapper{padding:0;position:relative}.swiper-nav-buttons{display:contents}.swiper-nav-button{position:absolute;top:50%;transform:translateY(-50%);z-index:10}.swiper-nav-button:first-child{left:-48px}.swiper-nav-button:last-child{right:-48px}::ng-deep .swiper-nav-button button{border-radius:50%!important;width:48px!important;height:48px!important;padding:0!important}@media (max-width: 767px){.swiper-nav-buttons{display:none}}::ng-deep .bookings-swiper .swiper-slide{width:100%;max-width:600px;height:auto;pointer-events:auto}@media (min-width: 768px){::ng-deep .bookings-swiper .swiper-slide{max-width:100%}}::ng-deep .bookings-swiper .swiper-slide ds-mobile-interactive-list-item-booking{width:100%;pointer-events:auto}.booking-slide-content{position:relative;width:100%}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileInteractiveListItemBookingComponent, selector: "ds-mobile-interactive-list-item-booking", inputs: ["thumbnail", "facilityTitle", "description", "bookingDate", "bookingTime", "availabilityStatus", "statusLabel", "variant", "align", "clickable", "showChevron", "enableLongPress", "moreActions"], outputs: ["bookingClick", "longPress"] }, { kind: "component", type: DsMobileSwiperComponent, selector: "ds-mobile-swiper", inputs: ["slideWidth", "gap", "pagination", "autoHeight", "progressiveOpacity", "progressiveScale"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: DsMobileFabComponent, selector: "ds-mobile-fab", inputs: ["icon", "position", "size", "ariaLabel", "disabled"], outputs: ["clicked"] }, { kind: "component", type: DsMobileLoaderOverlayComponent, selector: "ds-mobile-loader-overlay", inputs: ["spinnerSize", "borderRadius"] }] });
36008
+ `, isInline: true, styles: [".bookings-swiper-wrapper{padding:0;position:relative}.swiper-nav-buttons{display:contents}.swiper-nav-button{position:absolute;top:50%;transform:translateY(-50%);z-index:10}.swiper-nav-button:first-child{left:-48px}.swiper-nav-button:last-child{right:-48px}::ng-deep .swiper-nav-button button{border-radius:50%!important;width:48px!important;height:48px!important;padding:0!important}@media (max-width: 767px){.swiper-nav-buttons{display:none}}::ng-deep .bookings-swiper .swiper-slide{width:100%;max-width:600px;height:auto;pointer-events:auto}@media (min-width: 768px){::ng-deep .bookings-swiper .swiper-slide{max-width:100%}}::ng-deep .bookings-swiper .swiper-slide ds-mobile-interactive-list-item-booking{width:100%;pointer-events:auto}.booking-slide-content{position:relative;width:100%}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileInteractiveListItemBookingComponent, selector: "ds-mobile-interactive-list-item-booking", inputs: ["thumbnail", "facilityTitle", "description", "bookingDate", "bookingTime", "availabilityStatus", "statusLabel", "variant", "align", "clickable", "showChevron", "enableLongPress", "moreActions"], outputs: ["bookingClick", "longPress"] }, { kind: "component", type: DsMobileSwiperComponent, selector: "ds-mobile-swiper", inputs: ["slideWidth", "gap", "pagination", "autoHeight", "progressiveOpacity", "progressiveScale"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }, { kind: "component", type: DsMobileFabComponent, selector: "ds-mobile-fab", inputs: ["icon", "position", "size", "ariaLabel", "disabled"], outputs: ["clicked"] }, { kind: "component", type: DsMobileLoaderOverlayComponent, selector: "ds-mobile-loader-overlay", inputs: ["spinnerSize", "borderRadius"] }] });
34820
36009
  }
34821
36010
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileBookingPageComponent, decorators: [{
34822
36011
  type: Component,
@@ -34833,8 +36022,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
34833
36022
  <ds-mobile-page-main
34834
36023
  #pageComponent
34835
36024
  [title]="'Bookinger'"
36025
+ [notificationCount]="notificationService.unreadCount()"
34836
36026
  [avatarInitials]="userService.avatarInitials()"
34837
36027
  [avatarType]="userService.avatarType()"
36028
+ (notificationClick)="handleNotificationClick()"
34838
36029
  (refresh)="handleRefresh($event)">
34839
36030
 
34840
36031
  @if (pageComponent.isOffline()) {
@@ -36724,9 +37915,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
36724
37915
  class TenantChatPageComponent {
36725
37916
  modalCtrl = inject(ModalController);
36726
37917
  userService = inject(UserService);
37918
+ notificationModal = inject(DsMobileNotificationModalService);
37919
+ notificationService = inject(NotificationService);
36727
37920
  peerMessaging = inject(PeerMessagingService);
36728
37921
  peerChat = inject(PeerChatLauncherService);
36729
37922
  isPeerGroupConversation = isPeerGroupConversation;
37923
+ async handleNotificationClick() {
37924
+ const tapped = await this.notificationModal.open();
37925
+ if (tapped)
37926
+ console.log('Notification tapped:', tapped.type, tapped.id);
37927
+ }
36730
37928
  handleRefresh(event) {
36731
37929
  setTimeout(() => event.target.complete(), 1000);
36732
37930
  }
@@ -36753,8 +37951,10 @@ class TenantChatPageComponent {
36753
37951
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: TenantChatPageComponent, isStandalone: true, selector: "app-tenant-chat-page", host: { classAttribute: "ion-page" }, ngImport: i0, template: `
36754
37952
  <ds-mobile-page-main
36755
37953
  title="Beskeder"
37954
+ [notificationCount]="notificationService.unreadCount()"
36756
37955
  [avatarInitials]="userService.avatarInitials()"
36757
37956
  [avatarType]="userService.avatarType()"
37957
+ (notificationClick)="handleNotificationClick()"
36758
37958
  (refresh)="handleRefresh($event)">
36759
37959
 
36760
37960
  <ds-mobile-section contentGap="0px" padding="12px 20px 20px 20px" [showBorder]="false">
@@ -36813,7 +38013,7 @@ class TenantChatPageComponent {
36813
38013
  ariaLabel="Vælg beboer at skrive med"
36814
38014
  (clicked)="goToTenants()">
36815
38015
  </ds-mobile-fab>
36816
- `, isInline: true, styles: [".empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:64px 20px;text-align:center}.empty-state-image{width:96px;height:96px;margin-bottom:24px;opacity:.4}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin:0 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileInteractiveListItemMessageComponent, selector: "ds-mobile-interactive-list-item-message", inputs: ["senderName", "senderRole", "timestamp", "message", "avatarInitials", "avatarType", "avatarSrc", "unread", "unreadStyle", "clickable", "align", "showAvatarBadge", "groupStackMembers", "groupCustomAvatarUrl", "groupStackExcludeParticipantId"], outputs: ["messageClick", "longPress"] }, { kind: "component", type: DsMobileFabComponent, selector: "ds-mobile-fab", inputs: ["icon", "position", "size", "ariaLabel", "disabled"], outputs: ["clicked"] }] });
38016
+ `, isInline: true, styles: [".empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:64px 20px;text-align:center}.empty-state-image{width:96px;height:96px;margin-bottom:24px;opacity:.4}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin:0 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileInteractiveListItemMessageComponent, selector: "ds-mobile-interactive-list-item-message", inputs: ["senderName", "senderRole", "timestamp", "message", "avatarInitials", "avatarType", "avatarSrc", "unread", "unreadStyle", "clickable", "align", "showAvatarBadge", "groupStackMembers", "groupCustomAvatarUrl", "groupStackExcludeParticipantId"], outputs: ["messageClick", "longPress"] }, { kind: "component", type: DsMobileFabComponent, selector: "ds-mobile-fab", inputs: ["icon", "position", "size", "ariaLabel", "disabled"], outputs: ["clicked"] }] });
36817
38017
  }
36818
38018
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TenantChatPageComponent, decorators: [{
36819
38019
  type: Component,
@@ -36827,8 +38027,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
36827
38027
  }, template: `
36828
38028
  <ds-mobile-page-main
36829
38029
  title="Beskeder"
38030
+ [notificationCount]="notificationService.unreadCount()"
36830
38031
  [avatarInitials]="userService.avatarInitials()"
36831
38032
  [avatarType]="userService.avatarType()"
38033
+ (notificationClick)="handleNotificationClick()"
36832
38034
  (refresh)="handleRefresh($event)">
36833
38035
 
36834
38036
  <ds-mobile-section contentGap="0px" padding="12px 20px 20px 20px" [showBorder]="false">
@@ -36970,6 +38172,8 @@ const MOCK_VENDORS = [
36970
38172
  class ServicesPageComponent {
36971
38173
  pageComponent;
36972
38174
  vendorModal = inject(DsMobileServiceVendorModalService);
38175
+ notificationModal = inject(DsMobileNotificationModalService);
38176
+ notificationService = inject(NotificationService);
36973
38177
  newInquiryModal = inject(DsMobileNewInquiryModalService);
36974
38178
  inquiriesService = inject(InquiriesService);
36975
38179
  navCtrl = inject(NavController);
@@ -37016,6 +38220,11 @@ class ServicesPageComponent {
37016
38220
  },
37017
38221
  });
37018
38222
  }
38223
+ async handleNotificationClick() {
38224
+ const tapped = await this.notificationModal.open();
38225
+ if (tapped)
38226
+ console.log('Notification tapped:', tapped.type, tapped.id);
38227
+ }
37019
38228
  handleRefresh(event) {
37020
38229
  setTimeout(() => {
37021
38230
  event.target?.complete?.();
@@ -37026,8 +38235,10 @@ class ServicesPageComponent {
37026
38235
  <ds-mobile-page-main
37027
38236
  #pageComponent
37028
38237
  [title]="lbl.pageTitle"
38238
+ [notificationCount]="notificationService.unreadCount()"
37029
38239
  [avatarInitials]="userService.avatarInitials()"
37030
38240
  [avatarType]="userService.avatarType()"
38241
+ (notificationClick)="handleNotificationClick()"
37031
38242
  (refresh)="handleRefresh($event)"
37032
38243
  >
37033
38244
  @if (pageComponent.isOffline()) {
@@ -37059,7 +38270,7 @@ class ServicesPageComponent {
37059
38270
  </ds-mobile-section>
37060
38271
  }
37061
38272
  </ds-mobile-page-main>
37062
- `, isInline: true, styles: [".empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;text-align:center}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileIllustrationComponent, selector: "ds-mobile-illustration", inputs: ["variant", "size", "alt"] }, { kind: "component", type: DsMobileInteractiveListItemServiceComponent, selector: "ds-mobile-interactive-list-item-service", inputs: ["title", "description", "logo", "showChevron"], outputs: ["serviceClick"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }] });
38273
+ `, isInline: true, styles: [".empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;text-align:center}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"], dependencies: [{ kind: "component", type: DsMobilePageMainComponent, selector: "ds-mobile-page-main", inputs: ["title", "headerTitle", "headerSubtitle", "firstEntry", "avatarType", "avatarInitials", "avatarSrc", "avatarIconName", "notificationCount", "showRefresh", "showCondensedHeader", "scrollThreshold", "headerFadeDistance", "contentPadding", "profileMenuItems"], outputs: ["notificationClick", "avatarClick", "profileActionSelected", "refresh", "scroll"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "paddingDesktop", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileIllustrationComponent, selector: "ds-mobile-illustration", inputs: ["variant", "size", "alt"] }, { kind: "component", type: DsMobileInteractiveListItemServiceComponent, selector: "ds-mobile-interactive-list-item-service", inputs: ["title", "description", "logo", "showChevron"], outputs: ["serviceClick"] }, { kind: "component", type: DsMobileOfflineBannerComponent, selector: "ds-mobile-offline-banner", inputs: ["icon", "title", "message"] }] });
37063
38274
  }
37064
38275
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ServicesPageComponent, decorators: [{
37065
38276
  type: Component,
@@ -37073,8 +38284,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
37073
38284
  <ds-mobile-page-main
37074
38285
  #pageComponent
37075
38286
  [title]="lbl.pageTitle"
38287
+ [notificationCount]="notificationService.unreadCount()"
37076
38288
  [avatarInitials]="userService.avatarInitials()"
37077
38289
  [avatarType]="userService.avatarType()"
38290
+ (notificationClick)="handleNotificationClick()"
37078
38291
  (refresh)="handleRefresh($event)"
37079
38292
  >
37080
38293
  @if (pageComponent.isOffline()) {
@@ -37135,5 +38348,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
37135
38348
  * Generated bundle index. Do not edit.
37136
38349
  */
37137
38350
 
37138
- export { AcceptInvitePageComponent, ActionCommentComponent, ActionLikeComponent, AvatarUploadPageComponent, BaseModalService, ContentRowComponent, CreateAccountPageComponent, DEFAULT_SERVICE_PAGE_LABELS, DsAppIconComponent, DsAvatarWithBadgeComponent, DsLogoComponent, DsMobileAccessSheetComponent, DsMobileActionListItemComponent, DsMobileActionsBottomSheetComponent, DsMobileAddGroupTenantsModalComponent, DsMobileAppLoadingComponent, DsMobileAttachmentPreviewComponent, DsMobileBookingConfirmationWrapperComponent, DsMobileBookingModalComponent, DsMobileBookingModalService, DsMobileBookingSummaryComponent, DsMobileBottomSheetHeaderComponent, DsMobileBottomSheetService, DsMobileBottomSheetWrapperComponent, DsMobileCapacitySheetComponent, DsMobileCardInlineBannerComponent, DsMobileCardInlineComponent, DsMobileCardInlineContactComponent, DsMobileCardInlineFileComponent, DsMobileChatModalComponent, DsMobileChatModalService, DsMobileActionsBottomSheetComponent as DsMobileCommentActionsBottomSheetComponent, DsMobileCommentComponent, DsMobileCommunityAdminPickerComponent, DsMobileCommunityAdminsModalComponent, DsMobileConfirmationSheetComponent, DsMobileContactListItemComponent, DsMobileContentComponent, DsMobileCreateGroupModalComponent, DsMobileDropdownComponent, DsMobileEditGroupModalComponent, DsMobileEmptyStateComponent, DsMobileFabComponent, DsMobileFacilityArchiveConfirmationComponent, DsMobileFacilityCreationConfirmationWrapperComponent, DsMobileFacilityCreationModalComponent, DsMobileFacilityCreationModalService, DsMobileFacilityDeleteConfirmationComponent, DsMobileFacilityDetailModalComponent, DsMobileFacilityDetailModalService, DsMobileFileAttachmentComponent, DsMobileGlassSpinnerComponent, DsMobileGroupAvatarStackComponent, DsMobileGroupMembersModalComponent, DsMobileHandbookDetailModalComponent, DsMobileHandbookDetailModalService, DsMobileHandbookFolderComponent, DsMobileHandbookFolderMiniComponent, DsMobileHeaderContentComponent, DsMobileHeaderContentTileComponent, DsMobileIllustrationComponent, DsMobileImagePlaceholderComponent, DsMobileInlinePhotoComponent, DsMobileInlineTabsComponent, DsMobileInteractiveListItemBookingComponent, DsMobileInteractiveListItemInquiryComponent, DsMobileInteractiveListItemMessageComponent, DsMobileInteractiveListItemPostComponent, DsMobileLightboxImageComponent as DsMobileLightboxComponent, DsMobileLightboxFooterComponent, DsMobileLightboxHeaderComponent, DsMobileLightboxImageComponent, DsMobileLightboxImageWithDescriptionComponent, DsMobileLightboxPdfComponent, DsMobileLightboxService, DsMobileListItemComponent, DsMobileListItemStaticComponent, DsMobileListSearchComponent, DsMobileLoaderOverlayComponent, DsMobileLongPressDirective, DsMobileMediaActionsPanelComponent, DsMobileMessageBubbleComponent, DsMobileMessageComposerComponent, DsMobileModalBaseComponent, DsMobileModalService, DsMobileNewInquiryModalComponent, DsMobileNewInquiryModalService, DsMobileOfflineBannerComponent, DsMobilePageDetailsComponent, DsMobilePageMainComponent, DsMobilePillComponent, DsMobileActionsBottomSheetComponent as DsMobilePostActionsBottomSheetComponent, DsMobilePostComposerComponent, DsMobilePostCreateBottomSheetComponent, DsMobilePostDetailModalComponent, DsMobilePostDetailModalService, DsMobilePriceSheetComponent, DsMobileProfileActionsSheetComponent, DsMobilePromptBottomSheetComponent, DsMobilePropertyBannerComponent, DsMobileRichTextEditorComponent, DsMobileSectionComponent, DsMobileServiceVendorModalService, DsMobileServiceVendorSheetComponent, DsMobileSwiperComponent, DsMobileSwiperWithNavComponent, DsMobileSystemMessageBannerComponent, DsMobileTabBarComponent, DsMobileTabsComponent, DsMobileTenantPickerModalComponent, DsMobileWhenCanBookSheetComponent, DsMobileWhoCanBookSheetComponent, DsTextInputComponent, FamilyAccessPageComponent, FamilyAccessService, InquiriesService, InviteSuccessPageComponent, MediaPickerService, MobileBookingPageComponent, MobileCommunityPageComponent, MobileHandbookPageComponent, MobileHomePageComponent, MobileInquiriesPageComponent, MobileInquiryDetailPageComponent, MobileModalBase, MobilePageBase, MobilePostDetailPageComponent, MobileTabsExampleComponent, PageLoadingService, PostActionsComponent, PostAttachmentsComponent, PostContentComponent, PostCreatePageComponent, PostMediaComponent, PostPdfAttachmentComponent, PostTextComponent, PostsService, SectionHeaderComponent, ServicesPageComponent, SignInPageComponent, SignInToAcceptPageComponent, TenantChatPageComponent, TileContentComponent, TileIconComponent, TileLabelComponent, TileValueComponent, TrackingPermissionService, UserService, WhitelabelDemoModalComponent, WhitelabelDemoModalService, WhitelabelService, customBackTransition, customPageTransition };
38351
+ export { AcceptInvitePageComponent, ActionCommentComponent, ActionLikeComponent, AvatarUploadPageComponent, BaseModalService, ContentRowComponent, CreateAccountPageComponent, DEFAULT_SERVICE_PAGE_LABELS, DsAppIconComponent, DsAvatarWithBadgeComponent, DsLogoComponent, DsMobileAccessSheetComponent, DsMobileActionListItemComponent, DsMobileActionsBottomSheetComponent, DsMobileAddGroupTenantsModalComponent, DsMobileAppLoadingComponent, DsMobileAttachmentPreviewComponent, DsMobileBookingConfirmationWrapperComponent, DsMobileBookingModalComponent, DsMobileBookingModalService, DsMobileBookingSummaryComponent, DsMobileBottomSheetHeaderComponent, DsMobileBottomSheetService, DsMobileBottomSheetWrapperComponent, DsMobileCapacitySheetComponent, DsMobileCardInlineBannerComponent, DsMobileCardInlineComponent, DsMobileCardInlineContactComponent, DsMobileCardInlineFileComponent, DsMobileChatModalComponent, DsMobileChatModalService, DsMobileActionsBottomSheetComponent as DsMobileCommentActionsBottomSheetComponent, DsMobileCommentComponent, DsMobileCommunityAdminPickerComponent, DsMobileCommunityAdminsModalComponent, DsMobileConfirmationSheetComponent, DsMobileContactListItemComponent, DsMobileContentComponent, DsMobileCountBadgeComponent, DsMobileCreateGroupModalComponent, DsMobileDropdownComponent, DsMobileEditGroupModalComponent, DsMobileEmptyStateComponent, DsMobileFabComponent, DsMobileFacilityArchiveConfirmationComponent, DsMobileFacilityCreationConfirmationWrapperComponent, DsMobileFacilityCreationModalComponent, DsMobileFacilityCreationModalService, DsMobileFacilityDeleteConfirmationComponent, DsMobileFacilityDetailModalComponent, DsMobileFacilityDetailModalService, DsMobileFileAttachmentComponent, DsMobileGlassSpinnerComponent, DsMobileGroupAvatarStackComponent, DsMobileGroupMembersModalComponent, DsMobileHandbookDetailModalComponent, DsMobileHandbookDetailModalService, DsMobileHandbookFolderComponent, DsMobileHandbookFolderMiniComponent, DsMobileHeaderContentComponent, DsMobileHeaderContentTileComponent, DsMobileIllustrationComponent, DsMobileImagePlaceholderComponent, DsMobileInlinePhotoComponent, DsMobileInlineTabsComponent, DsMobileInteractiveListItemBookingComponent, DsMobileInteractiveListItemInquiryComponent, DsMobileInteractiveListItemMessageComponent, DsMobileInteractiveListItemPostComponent, DsMobileLightboxImageComponent as DsMobileLightboxComponent, DsMobileLightboxFooterComponent, DsMobileLightboxHeaderComponent, DsMobileLightboxImageComponent, DsMobileLightboxImageWithDescriptionComponent, DsMobileLightboxPdfComponent, DsMobileLightboxService, DsMobileListItemComponent, DsMobileListItemStaticComponent, DsMobileListSearchComponent, DsMobileLoaderOverlayComponent, DsMobileLongPressDirective, DsMobileMediaActionsPanelComponent, DsMobileMessageBubbleComponent, DsMobileMessageComposerComponent, DsMobileModalBaseComponent, DsMobileModalService, DsMobileNewInquiryModalComponent, DsMobileNewInquiryModalService, DsMobileNotificationButtonComponent, DsMobileNotificationModalComponent, DsMobileNotificationModalService, DsMobileNotificationPromptComponent, DsMobileOfflineBannerComponent, DsMobilePageDetailsComponent, DsMobilePageMainComponent, DsMobilePillComponent, DsMobileActionsBottomSheetComponent as DsMobilePostActionsBottomSheetComponent, DsMobilePostComposerComponent, DsMobilePostCreateBottomSheetComponent, DsMobilePostDetailModalComponent, DsMobilePostDetailModalService, DsMobilePriceSheetComponent, DsMobileProfileActionsSheetComponent, DsMobilePromptBottomSheetComponent, DsMobilePropertyBannerComponent, DsMobileRichTextEditorComponent, DsMobileSectionComponent, DsMobileServiceVendorModalService, DsMobileServiceVendorSheetComponent, DsMobileSwiperComponent, DsMobileSwiperWithNavComponent, DsMobileSystemMessageBannerComponent, DsMobileTabBarComponent, DsMobileTabsComponent, DsMobileTenantPickerModalComponent, DsMobileWhenCanBookSheetComponent, DsMobileWhoCanBookSheetComponent, DsTextInputComponent, FamilyAccessPageComponent, FamilyAccessService, InquiriesService, InviteSuccessPageComponent, MediaPickerService, MobileBookingPageComponent, MobileCommunityPageComponent, MobileHandbookPageComponent, MobileHomePageComponent, MobileInquiriesPageComponent, MobileInquiryDetailPageComponent, MobileModalBase, MobilePageBase, MobilePostDetailPageComponent, MobileTabsExampleComponent, NOTIFICATION_ICON_MAP, NotificationPromptService, NotificationService, PageLoadingService, PostActionsComponent, PostAttachmentsComponent, PostContentComponent, PostCreatePageComponent, PostMediaComponent, PostPdfAttachmentComponent, PostTextComponent, PostsService, RelativeTimePipe, SAMPLE_NOTIFICATIONS, SectionHeaderComponent, ServicesPageComponent, SignInPageComponent, SignInToAcceptPageComponent, TenantChatPageComponent, TileContentComponent, TileIconComponent, TileLabelComponent, TileValueComponent, TrackingPermissionService, UserService, VENDOR_MODAL_SERVICE, WhitelabelDemoModalComponent, WhitelabelDemoModalService, WhitelabelService, customBackTransition, customPageTransition, dateBucket };
37139
38352
  //# sourceMappingURL=propbinder-mobile-design.mjs.map