@memberjunction/ng-explorer-settings 5.42.0 → 5.44.0
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.
- package/dist/__tests__/role-management-agent-context.test.d.ts +2 -0
- package/dist/__tests__/role-management-agent-context.test.d.ts.map +1 -0
- package/dist/__tests__/role-management-agent-context.test.js +107 -0
- package/dist/__tests__/role-management-agent-context.test.js.map +1 -0
- package/dist/__tests__/user-management-agent-context.test.d.ts +2 -0
- package/dist/__tests__/user-management-agent-context.test.d.ts.map +1 -0
- package/dist/__tests__/user-management-agent-context.test.js +134 -0
- package/dist/__tests__/user-management-agent-context.test.js.map +1 -0
- package/dist/lib/application-management/application-dialog/application-dialog.component.js +176 -200
- package/dist/lib/application-management/application-dialog/application-dialog.component.js.map +1 -1
- package/dist/lib/application-management/application-management.component.d.ts +7 -0
- package/dist/lib/application-management/application-management.component.d.ts.map +1 -1
- package/dist/lib/application-management/application-management.component.js +49 -39
- package/dist/lib/application-management/application-management.component.js.map +1 -1
- package/dist/lib/application-settings/application-settings.component.js +58 -70
- package/dist/lib/application-settings/application-settings.component.js.map +1 -1
- package/dist/lib/entity-permissions/entity-permissions.component.d.ts +10 -0
- package/dist/lib/entity-permissions/entity-permissions.component.d.ts.map +1 -1
- package/dist/lib/entity-permissions/entity-permissions.component.js +32 -16
- package/dist/lib/entity-permissions/entity-permissions.component.js.map +1 -1
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.js +32 -33
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.js.map +1 -1
- package/dist/lib/module.d.ts +1 -1
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/module.js +13 -4
- package/dist/lib/module.js.map +1 -1
- package/dist/lib/notification-preferences/notification-preferences.component.js +30 -35
- package/dist/lib/notification-preferences/notification-preferences.component.js.map +1 -1
- package/dist/lib/role-management/role-dialog/role-dialog.component.js +26 -46
- package/dist/lib/role-management/role-dialog/role-dialog.component.js.map +1 -1
- package/dist/lib/role-management/role-management-agent-context.d.ts +96 -0
- package/dist/lib/role-management/role-management-agent-context.d.ts.map +1 -0
- package/dist/lib/role-management/role-management-agent-context.js +85 -0
- package/dist/lib/role-management/role-management-agent-context.js.map +1 -0
- package/dist/lib/role-management/role-management.component.d.ts +32 -0
- package/dist/lib/role-management/role-management.component.d.ts.map +1 -1
- package/dist/lib/role-management/role-management.component.js +236 -41
- package/dist/lib/role-management/role-management.component.js.map +1 -1
- package/dist/lib/sql-logging/sql-logging.component.js +151 -184
- package/dist/lib/sql-logging/sql-logging.component.js.map +1 -1
- package/dist/lib/user-app-config/user-app-config.component.js +80 -93
- package/dist/lib/user-app-config/user-app-config.component.js.map +1 -1
- package/dist/lib/user-management/user-dialog/user-dialog.component.js +20 -21
- package/dist/lib/user-management/user-dialog/user-dialog.component.js.map +1 -1
- package/dist/lib/user-management/user-management-agent-context.d.ts +107 -0
- package/dist/lib/user-management/user-management-agent-context.d.ts.map +1 -0
- package/dist/lib/user-management/user-management-agent-context.js +132 -0
- package/dist/lib/user-management/user-management-agent-context.js.map +1 -0
- package/dist/lib/user-management/user-management.component.d.ts +35 -0
- package/dist/lib/user-management/user-management.component.d.ts.map +1 -1
- package/dist/lib/user-management/user-management.component.js +311 -71
- package/dist/lib/user-management/user-management.component.js.map +1 -1
- package/dist/lib/user-profile-settings/user-profile-settings.component.js +52 -70
- package/dist/lib/user-profile-settings/user-profile-settings.component.js.map +1 -1
- package/package.json +19 -19
package/dist/lib/module.js
CHANGED
|
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
|
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
3
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
4
4
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
5
|
-
import { MJButtonDirective, MJPageHeaderInteriorComponent, MJPageBodyInteriorComponent, MJPageSearchComponent, MJFilterPopoverComponent, MJFilterPanelComponent, MJFilterChipComponent, MJRefreshButtonComponent, MJStatBadgeComponent, MJViewToggleComponent } from '@memberjunction/ng-ui-components';
|
|
5
|
+
import { MJButtonDirective, MJPageHeaderInteriorComponent, MJPageBodyInteriorComponent, MJPageSearchComponent, MJFilterPopoverComponent, MJFilterPanelComponent, MJFilterChipComponent, MJRefreshButtonComponent, MJStatBadgeComponent, MJViewToggleComponent, MJEmptyStateComponent, MJAlertComponent, MJAccordionModule } from '@memberjunction/ng-ui-components';
|
|
6
6
|
import { EntityPermissionsModule } from '@memberjunction/ng-entity-permissions';
|
|
7
7
|
import { EntityFormDialogModule } from '@memberjunction/ng-entity-form-dialog';
|
|
8
8
|
import { SimpleRecordListModule } from '@memberjunction/ng-simple-record-list';
|
|
@@ -56,7 +56,10 @@ export class ExplorerSettingsModule {
|
|
|
56
56
|
MJFilterChipComponent,
|
|
57
57
|
MJRefreshButtonComponent,
|
|
58
58
|
MJStatBadgeComponent,
|
|
59
|
-
MJViewToggleComponent
|
|
59
|
+
MJViewToggleComponent,
|
|
60
|
+
MJEmptyStateComponent,
|
|
61
|
+
MJAlertComponent,
|
|
62
|
+
MJAccordionModule] });
|
|
60
63
|
}
|
|
61
64
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ExplorerSettingsModule, [{
|
|
62
65
|
type: NgModule,
|
|
@@ -106,7 +109,10 @@ export class ExplorerSettingsModule {
|
|
|
106
109
|
MJFilterChipComponent,
|
|
107
110
|
MJRefreshButtonComponent,
|
|
108
111
|
MJStatBadgeComponent,
|
|
109
|
-
MJViewToggleComponent
|
|
112
|
+
MJViewToggleComponent,
|
|
113
|
+
MJEmptyStateComponent,
|
|
114
|
+
MJAlertComponent,
|
|
115
|
+
MJAccordionModule
|
|
110
116
|
],
|
|
111
117
|
exports: [
|
|
112
118
|
// Main settings container
|
|
@@ -176,7 +182,10 @@ export class ExplorerSettingsModule {
|
|
|
176
182
|
MJFilterChipComponent,
|
|
177
183
|
MJRefreshButtonComponent,
|
|
178
184
|
MJStatBadgeComponent,
|
|
179
|
-
MJViewToggleComponent
|
|
185
|
+
MJViewToggleComponent,
|
|
186
|
+
MJEmptyStateComponent,
|
|
187
|
+
MJAlertComponent,
|
|
188
|
+
MJAccordionModule], exports: [
|
|
180
189
|
// Main settings container
|
|
181
190
|
SettingsComponent,
|
|
182
191
|
// User-facing components
|
package/dist/lib/module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,OAAO,EACL,iBAAiB,EACjB,6BAA6B,EAC7B,2BAA2B,EAC3B,qBAAqB,EACrB,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/lib/module.ts"],"names":[],"mappings":"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","sourcesContent":["import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { DragDropModule } from '@angular/cdk/drag-drop';\n\nimport {\n MJButtonDirective,\n MJPageHeaderInteriorComponent,\n MJPageBodyInteriorComponent,\n MJPageSearchComponent,\n MJFilterPopoverComponent,\n MJFilterPanelComponent,\n MJFilterChipComponent,\n MJRefreshButtonComponent,\n MJStatBadgeComponent,\n MJViewToggleComponent,\n MJEmptyStateComponent,\n MJAlertComponent,\n MJAccordionModule\n} from '@memberjunction/ng-ui-components';\n\nimport { EntityPermissionsModule } from '@memberjunction/ng-entity-permissions';\nimport { EntityFormDialogModule } from '@memberjunction/ng-entity-form-dialog';\nimport { SimpleRecordListModule } from '@memberjunction/ng-simple-record-list';\nimport { JoinGridModule } from '@memberjunction/ng-join-grid';\nimport { CodeEditorModule } from '@memberjunction/ng-code-editor';\nimport { SharedGenericModule } from '@memberjunction/ng-shared-generic';\n\n// Shared module\nimport { SharedSettingsModule } from './shared/shared-settings.module';\n\n// Main settings container\nimport { SettingsComponent } from './settings/settings.component';\n\n// User-facing components\nimport { UserProfileSettingsComponent } from './user-profile-settings/user-profile-settings.component';\nimport { UserAppConfigComponent } from './user-app-config/user-app-config.component';\nimport { NotificationPreferencesComponent } from './notification-preferences/notification-preferences.component';\n\n// New user settings components\nimport { GeneralSettingsComponent } from './general-settings/general-settings.component';\nimport { AccountInfoComponent } from './account-info/account-info.component';\nimport { ApplicationSettingsComponent } from './application-settings/application-settings.component';\nimport { AppearanceSettingsComponent } from './appearance-settings/appearance-settings.component';\n\n// Admin components (used by Admin app dashboards)\nimport { SqlLoggingComponent } from './sql-logging/sql-logging.component';\nimport { UserManagementComponent } from './user-management/user-management.component';\nimport { RoleManagementComponent } from './role-management/role-management.component';\nimport { ApplicationManagementComponent } from './application-management/application-management.component';\nimport { EntityPermissionsComponent } from './entity-permissions/entity-permissions.component';\n\n// Admin dialog components\nimport { RoleDialogComponent } from './role-management/role-dialog/role-dialog.component';\nimport { UserDialogComponent } from './user-management/user-dialog/user-dialog.component';\nimport { PermissionDialogComponent } from './entity-permissions/permission-dialog/permission-dialog.component';\nimport { ApplicationDialogComponent } from './application-management/application-dialog/application-dialog.component';\n\n@NgModule({\n declarations: [\n // Main settings container\n SettingsComponent,\n // User-facing components\n UserProfileSettingsComponent,\n UserAppConfigComponent,\n NotificationPreferencesComponent,\n // New user settings components\n GeneralSettingsComponent,\n AccountInfoComponent,\n ApplicationSettingsComponent,\n AppearanceSettingsComponent,\n // Admin components (used by Admin app dashboards)\n SqlLoggingComponent,\n UserManagementComponent,\n RoleManagementComponent,\n ApplicationManagementComponent,\n EntityPermissionsComponent,\n // Admin dialog components\n RoleDialogComponent,\n UserDialogComponent,\n PermissionDialogComponent,\n ApplicationDialogComponent\n ],\n imports: [\n CommonModule,\n FormsModule,\n ReactiveFormsModule,\n DragDropModule,\n EntityPermissionsModule,\n EntityFormDialogModule,\n SimpleRecordListModule,\n JoinGridModule,\n CodeEditorModule,\n SharedSettingsModule,\n SharedGenericModule,\n MJButtonDirective,\n MJPageHeaderInteriorComponent,\n MJPageBodyInteriorComponent,\n MJPageSearchComponent,\n MJFilterPopoverComponent,\n MJFilterPanelComponent,\n MJFilterChipComponent,\n MJRefreshButtonComponent,\n MJStatBadgeComponent,\n MJViewToggleComponent,\n MJEmptyStateComponent,\n MJAlertComponent,\n MJAccordionModule\n ],\n exports: [\n // Main settings container\n SettingsComponent,\n // User-facing components\n UserProfileSettingsComponent,\n UserAppConfigComponent,\n NotificationPreferencesComponent,\n // New user settings components\n GeneralSettingsComponent,\n AccountInfoComponent,\n ApplicationSettingsComponent,\n AppearanceSettingsComponent,\n // Admin components (used by Admin app dashboards)\n SqlLoggingComponent,\n UserManagementComponent,\n RoleManagementComponent,\n ApplicationManagementComponent,\n EntityPermissionsComponent,\n // Admin dialog components\n RoleDialogComponent,\n UserDialogComponent,\n PermissionDialogComponent,\n ApplicationDialogComponent\n ]\n})\nexport class ExplorerSettingsModule { }\n"]}
|
|
@@ -6,28 +6,23 @@ import * as i0 from "@angular/core";
|
|
|
6
6
|
import * as i1 from "@memberjunction/ng-shared";
|
|
7
7
|
import * as i2 from "@angular/forms";
|
|
8
8
|
import * as i3 from "@memberjunction/ng-shared-generic";
|
|
9
|
+
import * as i4 from "@memberjunction/ng-ui-components";
|
|
9
10
|
const _forTrack0 = ($index, $item) => $item.type.ID;
|
|
10
11
|
function NotificationPreferencesComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
11
12
|
i0.ɵɵelement(0, "mj-loading", 1);
|
|
12
13
|
} }
|
|
13
14
|
function NotificationPreferencesComponent_Conditional_2_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
14
|
-
i0.ɵɵ
|
|
15
|
-
i0.ɵɵelement(1, "i", 6);
|
|
16
|
-
i0.ɵɵelementStart(2, "p");
|
|
17
|
-
i0.ɵɵtext(3, "No notification types configured yet.");
|
|
18
|
-
i0.ɵɵelementEnd()();
|
|
15
|
+
i0.ɵɵelement(0, "mj-empty-state", 3);
|
|
19
16
|
} }
|
|
20
17
|
function NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Conditional_29_Template(rf, ctx) { if (rf & 1) {
|
|
21
|
-
i0.ɵɵelementStart(0, "
|
|
22
|
-
i0.ɵɵ
|
|
23
|
-
i0.ɵɵ
|
|
24
|
-
i0.ɵɵtext(3, "This notification type's delivery channels cannot be customized.");
|
|
25
|
-
i0.ɵɵelementEnd()();
|
|
18
|
+
i0.ɵɵelementStart(0, "mj-alert", 21);
|
|
19
|
+
i0.ɵɵtext(1, "This notification type's delivery channels cannot be customized.");
|
|
20
|
+
i0.ɵɵelementEnd();
|
|
26
21
|
} }
|
|
27
22
|
function NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Conditional_30_Template(rf, ctx) { if (rf & 1) {
|
|
28
|
-
i0.ɵɵelementStart(0, "div",
|
|
29
|
-
i0.ɵɵelement(1, "i",
|
|
30
|
-
i0.ɵɵelementStart(2, "span",
|
|
23
|
+
i0.ɵɵelementStart(0, "div", 22);
|
|
24
|
+
i0.ɵɵelement(1, "i", 23);
|
|
25
|
+
i0.ɵɵelementStart(2, "span", 24);
|
|
31
26
|
i0.ɵɵtext(3);
|
|
32
27
|
i0.ɵɵelementEnd()();
|
|
33
28
|
} if (rf & 2) {
|
|
@@ -38,45 +33,45 @@ function NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Cond
|
|
|
38
33
|
} }
|
|
39
34
|
function NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
40
35
|
const _r1 = i0.ɵɵgetCurrentView();
|
|
41
|
-
i0.ɵɵelementStart(0, "article",
|
|
36
|
+
i0.ɵɵelementStart(0, "article", 7)(1, "div", 8)(2, "div", 9);
|
|
42
37
|
i0.ɵɵelement(3, "i");
|
|
43
38
|
i0.ɵɵelementEnd();
|
|
44
|
-
i0.ɵɵelementStart(4, "div",
|
|
39
|
+
i0.ɵɵelementStart(4, "div", 10)(5, "h4");
|
|
45
40
|
i0.ɵɵtext(6);
|
|
46
41
|
i0.ɵɵelementEnd();
|
|
47
|
-
i0.ɵɵelementStart(7, "p",
|
|
42
|
+
i0.ɵɵelementStart(7, "p", 11);
|
|
48
43
|
i0.ɵɵtext(8);
|
|
49
44
|
i0.ɵɵelementEnd()()();
|
|
50
|
-
i0.ɵɵelementStart(9, "div",
|
|
45
|
+
i0.ɵɵelementStart(9, "div", 12)(10, "div", 13)(11, "label", 14);
|
|
51
46
|
i0.ɵɵtext(12, "Delivery Channels");
|
|
52
47
|
i0.ɵɵelementEnd();
|
|
53
|
-
i0.ɵɵelementStart(13, "div",
|
|
48
|
+
i0.ɵɵelementStart(13, "div", 15)(14, "label", 16)(15, "input", 17);
|
|
54
49
|
i0.ɵɵtwoWayListener("ngModelChange", function NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Template_input_ngModelChange_15_listener($event) { const vm_r2 = i0.ɵɵrestoreView(_r1).$implicit; i0.ɵɵtwoWayBindingSet(vm_r2.inAppEnabled, $event) || (vm_r2.inAppEnabled = $event); return i0.ɵɵresetView($event); });
|
|
55
50
|
i0.ɵɵlistener("change", function NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Template_input_change_15_listener() { const vm_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.onChannelChange(vm_r2)); });
|
|
56
51
|
i0.ɵɵelementEnd();
|
|
57
|
-
i0.ɵɵelement(16, "i",
|
|
52
|
+
i0.ɵɵelement(16, "i", 18);
|
|
58
53
|
i0.ɵɵelementStart(17, "span");
|
|
59
54
|
i0.ɵɵtext(18, "In-App");
|
|
60
55
|
i0.ɵɵelementEnd()();
|
|
61
|
-
i0.ɵɵelementStart(19, "label",
|
|
56
|
+
i0.ɵɵelementStart(19, "label", 16)(20, "input", 17);
|
|
62
57
|
i0.ɵɵtwoWayListener("ngModelChange", function NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Template_input_ngModelChange_20_listener($event) { const vm_r2 = i0.ɵɵrestoreView(_r1).$implicit; i0.ɵɵtwoWayBindingSet(vm_r2.emailEnabled, $event) || (vm_r2.emailEnabled = $event); return i0.ɵɵresetView($event); });
|
|
63
58
|
i0.ɵɵlistener("change", function NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Template_input_change_20_listener() { const vm_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.onChannelChange(vm_r2)); });
|
|
64
59
|
i0.ɵɵelementEnd();
|
|
65
|
-
i0.ɵɵelement(21, "i",
|
|
60
|
+
i0.ɵɵelement(21, "i", 19);
|
|
66
61
|
i0.ɵɵelementStart(22, "span");
|
|
67
62
|
i0.ɵɵtext(23, "Email");
|
|
68
63
|
i0.ɵɵelementEnd()();
|
|
69
|
-
i0.ɵɵelementStart(24, "label",
|
|
64
|
+
i0.ɵɵelementStart(24, "label", 16)(25, "input", 17);
|
|
70
65
|
i0.ɵɵtwoWayListener("ngModelChange", function NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Template_input_ngModelChange_25_listener($event) { const vm_r2 = i0.ɵɵrestoreView(_r1).$implicit; i0.ɵɵtwoWayBindingSet(vm_r2.smsEnabled, $event) || (vm_r2.smsEnabled = $event); return i0.ɵɵresetView($event); });
|
|
71
66
|
i0.ɵɵlistener("change", function NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Template_input_change_25_listener() { const vm_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.onChannelChange(vm_r2)); });
|
|
72
67
|
i0.ɵɵelementEnd();
|
|
73
|
-
i0.ɵɵelement(26, "i",
|
|
68
|
+
i0.ɵɵelement(26, "i", 20);
|
|
74
69
|
i0.ɵɵelementStart(27, "span");
|
|
75
70
|
i0.ɵɵtext(28, "SMS");
|
|
76
71
|
i0.ɵɵelementEnd()()();
|
|
77
|
-
i0.ɵɵconditionalCreate(29, NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Conditional_29_Template,
|
|
72
|
+
i0.ɵɵconditionalCreate(29, NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Conditional_29_Template, 2, 0, "mj-alert", 21);
|
|
78
73
|
i0.ɵɵelementEnd();
|
|
79
|
-
i0.ɵɵconditionalCreate(30, NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Conditional_30_Template, 4, 1, "div",
|
|
74
|
+
i0.ɵɵconditionalCreate(30, NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Conditional_30_Template, 4, 1, "div", 22);
|
|
80
75
|
i0.ɵɵelementEnd()();
|
|
81
76
|
} if (rf & 2) {
|
|
82
77
|
const vm_r2 = ctx.$implicit;
|
|
@@ -118,7 +113,7 @@ function NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Temp
|
|
|
118
113
|
} }
|
|
119
114
|
function NotificationPreferencesComponent_Conditional_2_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
120
115
|
i0.ɵɵelementStart(0, "div", 4);
|
|
121
|
-
i0.ɵɵrepeaterCreate(1, NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Template, 31, 27, "article",
|
|
116
|
+
i0.ɵɵrepeaterCreate(1, NotificationPreferencesComponent_Conditional_2_Conditional_2_For_2_Template, 31, 27, "article", 6, _forTrack0);
|
|
122
117
|
i0.ɵɵelementEnd();
|
|
123
118
|
} if (rf & 2) {
|
|
124
119
|
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
@@ -126,20 +121,20 @@ function NotificationPreferencesComponent_Conditional_2_Conditional_2_Template(r
|
|
|
126
121
|
i0.ɵɵrepeater(ctx_r2.viewModels);
|
|
127
122
|
} }
|
|
128
123
|
function NotificationPreferencesComponent_Conditional_2_Conditional_3_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
129
|
-
i0.ɵɵelement(0, "i",
|
|
124
|
+
i0.ɵɵelement(0, "i", 27);
|
|
130
125
|
} }
|
|
131
126
|
function NotificationPreferencesComponent_Conditional_2_Conditional_3_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
132
|
-
i0.ɵɵelement(0, "i",
|
|
127
|
+
i0.ɵɵelement(0, "i", 28);
|
|
133
128
|
} }
|
|
134
129
|
function NotificationPreferencesComponent_Conditional_2_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
135
130
|
const _r4 = i0.ɵɵgetCurrentView();
|
|
136
|
-
i0.ɵɵelementStart(0, "div", 5)(1, "button",
|
|
131
|
+
i0.ɵɵelementStart(0, "div", 5)(1, "button", 25);
|
|
137
132
|
i0.ɵɵlistener("click", function NotificationPreferencesComponent_Conditional_2_Conditional_3_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.cancel()); });
|
|
138
133
|
i0.ɵɵtext(2, " Cancel ");
|
|
139
134
|
i0.ɵɵelementEnd();
|
|
140
|
-
i0.ɵɵelementStart(3, "button",
|
|
135
|
+
i0.ɵɵelementStart(3, "button", 26);
|
|
141
136
|
i0.ɵɵlistener("click", function NotificationPreferencesComponent_Conditional_2_Conditional_3_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.save()); });
|
|
142
|
-
i0.ɵɵconditionalCreate(4, NotificationPreferencesComponent_Conditional_2_Conditional_3_Conditional_4_Template, 1, 0, "i",
|
|
137
|
+
i0.ɵɵconditionalCreate(4, NotificationPreferencesComponent_Conditional_2_Conditional_3_Conditional_4_Template, 1, 0, "i", 27)(5, NotificationPreferencesComponent_Conditional_2_Conditional_3_Conditional_5_Template, 1, 0, "i", 28);
|
|
143
138
|
i0.ɵɵelementStart(6, "span");
|
|
144
139
|
i0.ɵɵtext(7);
|
|
145
140
|
i0.ɵɵelementEnd()()();
|
|
@@ -158,7 +153,7 @@ function NotificationPreferencesComponent_Conditional_2_Conditional_3_Template(r
|
|
|
158
153
|
} }
|
|
159
154
|
function NotificationPreferencesComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
160
155
|
i0.ɵɵelementStart(0, "div", 2);
|
|
161
|
-
i0.ɵɵconditionalCreate(1, NotificationPreferencesComponent_Conditional_2_Conditional_1_Template,
|
|
156
|
+
i0.ɵɵconditionalCreate(1, NotificationPreferencesComponent_Conditional_2_Conditional_1_Template, 1, 0, "mj-empty-state", 3)(2, NotificationPreferencesComponent_Conditional_2_Conditional_2_Template, 3, 0, "div", 4);
|
|
162
157
|
i0.ɵɵconditionalCreate(3, NotificationPreferencesComponent_Conditional_2_Conditional_3_Template, 8, 6, "div", 5);
|
|
163
158
|
i0.ɵɵelementEnd();
|
|
164
159
|
} if (rf & 2) {
|
|
@@ -368,18 +363,18 @@ export class NotificationPreferencesComponent extends BaseAngularComponent {
|
|
|
368
363
|
return type.AllowUserPreference !== false;
|
|
369
364
|
}
|
|
370
365
|
static ɵfac = function NotificationPreferencesComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || NotificationPreferencesComponent)(i0.ɵɵdirectiveInject(i1.SharedService)); };
|
|
371
|
-
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: NotificationPreferencesComponent, selectors: [["mj-notification-preferences"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 3, vars: 1, consts: [[1, "notification-preferences-container"], ["text", "Loading notification preferences..."], [1, "content"], [
|
|
366
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: NotificationPreferencesComponent, selectors: [["mj-notification-preferences"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 3, vars: 1, consts: [[1, "notification-preferences-container"], ["text", "Loading notification preferences..."], [1, "content"], ["Icon", "fa-solid fa-bell-slash", "Title", "No notification types configured yet."], [1, "notification-types-grid"], ["role", "group", "aria-label", "Save or cancel changes", 1, "actions"], ["role", "region", 1, "notification-type-card", 3, "changed"], ["role", "region", 1, "notification-type-card"], [1, "card-header"], [1, "icon-wrapper"], [1, "header-text"], [1, "type-description"], [1, "card-body"], [1, "preference-row"], [1, "delivery-label"], ["role", "group", 1, "delivery-checkboxes"], [1, "channel-checkbox"], ["type", "checkbox", 3, "ngModelChange", "change", "ngModel", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-bell"], ["aria-hidden", "true", 1, "fa-solid", "fa-envelope"], ["aria-hidden", "true", 1, "fa-solid", "fa-mobile"], ["Variant", "info", 1, "info-message"], [1, "metadata-row"], ["aria-hidden", "true", 1, "fa-solid", "fa-clock"], [1, "metadata-text"], ["type", "button", 1, "btn", "btn-secondary", 3, "click", "disabled"], ["type", "button", 1, "btn", "btn-primary", 3, "click", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-spinner", "fa-spin"], ["aria-hidden", "true", 1, "fa-solid", "fa-save"]], template: function NotificationPreferencesComponent_Template(rf, ctx) { if (rf & 1) {
|
|
372
367
|
i0.ɵɵelementStart(0, "div", 0);
|
|
373
368
|
i0.ɵɵconditionalCreate(1, NotificationPreferencesComponent_Conditional_1_Template, 1, 0, "mj-loading", 1)(2, NotificationPreferencesComponent_Conditional_2_Template, 4, 2, "div", 2);
|
|
374
369
|
i0.ɵɵelementEnd();
|
|
375
370
|
} if (rf & 2) {
|
|
376
371
|
i0.ɵɵadvance();
|
|
377
372
|
i0.ɵɵconditional(ctx.loading ? 1 : 2);
|
|
378
|
-
} }, dependencies: [i2.CheckboxControlValueAccessor, i2.NgControlStatus, i2.NgModel, i3.LoadingComponent], styles: ["\n\n\n\n\n\n\n\n[_nghost-%COMP%] {\n display: block;\n width: 100%;\n}\n\n\n\n\n\n.notification-preferences-container[_ngcontent-%COMP%] {\n padding: 1.5rem;\n max-width: 1200px;\n margin: 0 auto;\n background: var(--mj-bg-page);\n}\n\n\n\n\n\n.content[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_fadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n\n\n\n\n.empty-state[_ngcontent-%COMP%] {\n text-align: center;\n padding: 3rem 1.5rem;\n}\n\n.empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 4rem;\n color: var(--mj-border-strong);\n margin-bottom: 1.5rem;\n display: block;\n}\n\n.empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n font-size: 1.125rem;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin: 0;\n}\n\n\n\n\n\n.notification-types-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));\n gap: 1.25rem;\n margin-bottom: 1.5rem;\n}\n\n\n\n\n\n.notification-type-card[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-radius: 12px;\n border-left: 4px solid var(--mj-border-strong);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.notification-type-card[_ngcontent-%COMP%]:hover {\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n border-color: var(--mj-brand-primary);\n}\n\n.notification-type-card.changed[_ngcontent-%COMP%] {\n border-left-color: var(--mj-brand-primary) !important;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.08);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n\n\n\n\n.card-header[_ngcontent-%COMP%] {\n display: flex;\n gap: 1rem;\n padding: 1.25rem;\n border-bottom: 1px solid var(--mj-border-strong);\n align-items: flex-start;\n}\n\n.icon-wrapper[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: transform 0.2s;\n}\n\n.notification-type-card[_ngcontent-%COMP%]:hover .icon-wrapper[_ngcontent-%COMP%] {\n transform: scale(1.05);\n}\n\n.icon-wrapper[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1.5rem;\n}\n\n.header-text[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.header-text[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 0.375rem 0;\n font-size: 1.125rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.type-description[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 0.875rem;\n color: var(--mj-text-muted);\n line-height: 1.4;\n}\n\n\n\n\n\n.card-body[_ngcontent-%COMP%] {\n padding: 1.25rem;\n}\n\n.preference-row[_ngcontent-%COMP%] {\n margin-bottom: 1rem;\n}\n\n.preference-row[_ngcontent-%COMP%]:last-child {\n margin-bottom: 0;\n}\n\n.delivery-label[_ngcontent-%COMP%] {\n display: block;\n font-weight: 600;\n margin-bottom: 0.75rem;\n font-size: 0.875rem;\n color: var(--mj-text-primary);\n letter-spacing: 0.01em;\n}\n\n\n\n\n\n.delivery-checkboxes[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n}\n\n.channel-checkbox[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n padding: 0.75rem 1rem;\n border: 2px solid var(--mj-border-strong);\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n user-select: none;\n background: var(--mj-bg-page);\n min-height: 44px;\n}\n\n.channel-checkbox[_ngcontent-%COMP%] input[type=\"checkbox\"][_ngcontent-%COMP%] {\n width: 18px;\n height: 18px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n margin: 0;\n}\n\n.channel-checkbox[_ngcontent-%COMP%] input[type=\"checkbox\"][_ngcontent-%COMP%]:disabled {\n cursor: not-allowed;\n}\n\n.channel-checkbox[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 1rem;\n transition: color 0.2s;\n}\n\n.channel-checkbox[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.channel-checkbox[_ngcontent-%COMP%]:hover:not(:has(input:disabled)) {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.channel-checkbox[_ngcontent-%COMP%]:hover:not(:has(input:disabled)) i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.channel-checkbox[_ngcontent-%COMP%]:has(input:checked) {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.channel-checkbox[_ngcontent-%COMP%]:has(input:checked) i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.channel-checkbox[_ngcontent-%COMP%]:has(input:disabled) {\n opacity: 0.5;\n cursor: not-allowed;\n background: var(--mj-bg-surface-sunken);\n}\n\n.channel-checkbox[_ngcontent-%COMP%]:focus-within {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n\n\n\n\n.info-message[_ngcontent-%COMP%] {\n margin-top: 0.75rem;\n padding: 0.75rem;\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n border-left: 3px solid var(--mj-status-warning);\n border-radius: 8px;\n font-size: 0.8125rem;\n display: flex;\n gap: 0.75rem;\n align-items: flex-start;\n line-height: 1.4;\n color: var(--mj-text-primary);\n}\n\n.info-message[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n font-size: 1rem;\n margin-top: 0.1rem;\n flex-shrink: 0;\n}\n\n\n\n\n\n.metadata-row[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.625rem;\n align-items: center;\n font-size: 0.8125rem;\n color: var(--mj-text-muted);\n padding-top: 0.75rem;\n margin-top: 0.75rem;\n border-top: 1px solid var(--mj-border-strong);\n}\n\n.metadata-row[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 0.875rem;\n flex-shrink: 0;\n}\n\n.metadata-text[_ngcontent-%COMP%] {\n line-height: 1.4;\n}\n\n\n\n\n\n.actions[_ngcontent-%COMP%] {\n display: flex;\n justify-content: flex-start;\n gap: 0.75rem;\n padding: 1.25rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: 12px;\n border: 1px solid var(--mj-border-strong);\n margin-top: 1.5rem;\n}\n\n\n\n\n\n.btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.75rem 1.5rem;\n border: none;\n border-radius: 9999px;\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n white-space: nowrap;\n min-height: 44px;\n}\n\n.btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.38;\n cursor: not-allowed;\n}\n\n.btn[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n.btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1rem;\n}\n\n.btn-secondary[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n border: 1px solid var(--mj-border-strong);\n}\n\n.btn-secondary[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n border-color: var(--mj-brand-primary);\n}\n\n.btn-secondary[_ngcontent-%COMP%]:active:not(:disabled) {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-primary[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.btn-primary[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n}\n\n.btn-primary[_ngcontent-%COMP%]:active:not(:disabled) {\n background: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-ghost[_ngcontent-%COMP%] {\n background: transparent;\n color: var(--mj-text-muted);\n border: none;\n}\n\n.btn-ghost[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n}\n\n.btn-ghost[_ngcontent-%COMP%]:active:not(:disabled) {\n background: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-danger[_ngcontent-%COMP%] {\n background: var(--mj-status-error);\n color: var(--mj-bg-surface);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.btn-danger[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-status-error);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n}\n\n.btn-danger[_ngcontent-%COMP%]:active:not(:disabled) {\n background: var(--mj-status-error);\n transform: scale(0.98);\n}\n\n.btn-sm[_ngcontent-%COMP%] {\n padding: 0.5rem 0.875rem;\n font-size: 0.8125rem;\n min-height: 36px;\n}\n\n\n\n\n\n.badge-system[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n border: 1px solid var(--mj-status-warning);\n}\n\n.badge-custom[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n border: 1px solid var(--mj-status-success);\n}\n\n\n\n\n\nbutton[_ngcontent-%COMP%]:focus-visible, \ninput[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n\n\n\n\n@media (prefers-reduced-motion: reduce) {\n *[_ngcontent-%COMP%], \n *[_ngcontent-%COMP%]::before, \n *[_ngcontent-%COMP%]::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n\n\n\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n\n\n\n\n@media (max-width: 639px) {\n .notification-preferences-container[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .notification-types-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n gap: 1rem;\n }\n\n .card-header[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .card-body[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .delivery-checkboxes[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .channel-checkbox[_ngcontent-%COMP%] {\n width: 100%;\n }\n\n .actions[_ngcontent-%COMP%] {\n flex-direction: column;\n padding: 1rem;\n }\n\n .actions[_ngcontent-%COMP%] .btn[_ngcontent-%COMP%] {\n width: 100%;\n justify-content: center;\n }\n}\n\n@media (min-width: 640px) and (max-width: 767px) {\n .notification-types-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n}\n\n@media (min-width: 768px) {\n .notification-preferences-container[_ngcontent-%COMP%] {\n padding: 2rem;\n }\n\n .notification-types-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));\n }\n}\n\n@media (min-width: 1024px) {\n .notification-types-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));\n gap: 1.5rem;\n }\n}"] });
|
|
373
|
+
} }, dependencies: [i2.CheckboxControlValueAccessor, i2.NgControlStatus, i2.NgModel, i3.LoadingComponent, i4.MJEmptyStateComponent, i4.MJAlertComponent], styles: ["\n\n\n\n\n\n\n\n[_nghost-%COMP%] {\n display: block;\n width: 100%;\n}\n\n\n\n\n\n.notification-preferences-container[_ngcontent-%COMP%] {\n padding: 1.5rem;\n max-width: 1200px;\n margin: 0 auto;\n background: var(--mj-bg-page);\n}\n\n\n\n\n\n.content[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_fadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n\n\n\n\n\n.notification-types-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));\n gap: 1.25rem;\n margin-bottom: 1.5rem;\n}\n\n\n\n\n\n.notification-type-card[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-radius: 12px;\n border-left: 4px solid var(--mj-border-strong);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.notification-type-card[_ngcontent-%COMP%]:hover {\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n border-color: var(--mj-brand-primary);\n}\n\n.notification-type-card.changed[_ngcontent-%COMP%] {\n border-left-color: var(--mj-brand-primary) !important;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.08);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n\n\n\n\n.card-header[_ngcontent-%COMP%] {\n display: flex;\n gap: 1rem;\n padding: 1.25rem;\n border-bottom: 1px solid var(--mj-border-strong);\n align-items: flex-start;\n}\n\n.icon-wrapper[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: transform 0.2s;\n}\n\n.notification-type-card[_ngcontent-%COMP%]:hover .icon-wrapper[_ngcontent-%COMP%] {\n transform: scale(1.05);\n}\n\n.icon-wrapper[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1.5rem;\n}\n\n.header-text[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.header-text[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 0.375rem 0;\n font-size: 1.125rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.type-description[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 0.875rem;\n color: var(--mj-text-muted);\n line-height: 1.4;\n}\n\n\n\n\n\n.card-body[_ngcontent-%COMP%] {\n padding: 1.25rem;\n}\n\n.preference-row[_ngcontent-%COMP%] {\n margin-bottom: 1rem;\n}\n\n.preference-row[_ngcontent-%COMP%]:last-child {\n margin-bottom: 0;\n}\n\n.delivery-label[_ngcontent-%COMP%] {\n display: block;\n font-weight: 600;\n margin-bottom: 0.75rem;\n font-size: 0.875rem;\n color: var(--mj-text-primary);\n letter-spacing: 0.01em;\n}\n\n\n\n\n\n.delivery-checkboxes[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n}\n\n.channel-checkbox[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n padding: 0.75rem 1rem;\n border: 2px solid var(--mj-border-strong);\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n user-select: none;\n background: var(--mj-bg-page);\n min-height: 44px;\n}\n\n.channel-checkbox[_ngcontent-%COMP%] input[type=\"checkbox\"][_ngcontent-%COMP%] {\n width: 18px;\n height: 18px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n margin: 0;\n}\n\n.channel-checkbox[_ngcontent-%COMP%] input[type=\"checkbox\"][_ngcontent-%COMP%]:disabled {\n cursor: not-allowed;\n}\n\n.channel-checkbox[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 1rem;\n transition: color 0.2s;\n}\n\n.channel-checkbox[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.channel-checkbox[_ngcontent-%COMP%]:hover:not(:has(input:disabled)) {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.channel-checkbox[_ngcontent-%COMP%]:hover:not(:has(input:disabled)) i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.channel-checkbox[_ngcontent-%COMP%]:has(input:checked) {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.channel-checkbox[_ngcontent-%COMP%]:has(input:checked) i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.channel-checkbox[_ngcontent-%COMP%]:has(input:disabled) {\n opacity: 0.5;\n cursor: not-allowed;\n background: var(--mj-bg-surface-sunken);\n}\n\n.channel-checkbox[_ngcontent-%COMP%]:focus-within {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n\n\n\n\n.info-message[_ngcontent-%COMP%] { margin-top: 0.75rem; }\n\n\n\n\n\n.metadata-row[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.625rem;\n align-items: center;\n font-size: 0.8125rem;\n color: var(--mj-text-muted);\n padding-top: 0.75rem;\n margin-top: 0.75rem;\n border-top: 1px solid var(--mj-border-strong);\n}\n\n.metadata-row[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 0.875rem;\n flex-shrink: 0;\n}\n\n.metadata-text[_ngcontent-%COMP%] {\n line-height: 1.4;\n}\n\n\n\n\n\n.actions[_ngcontent-%COMP%] {\n display: flex;\n justify-content: flex-start;\n gap: 0.75rem;\n padding: 1.25rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: 12px;\n border: 1px solid var(--mj-border-strong);\n margin-top: 1.5rem;\n}\n\n\n\n\n\n.btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.75rem 1.5rem;\n border: none;\n border-radius: 9999px;\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n white-space: nowrap;\n min-height: 44px;\n}\n\n.btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.38;\n cursor: not-allowed;\n}\n\n.btn[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n.btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1rem;\n}\n\n.btn-secondary[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n border: 1px solid var(--mj-border-strong);\n}\n\n.btn-secondary[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n border-color: var(--mj-brand-primary);\n}\n\n.btn-secondary[_ngcontent-%COMP%]:active:not(:disabled) {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-primary[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.btn-primary[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n}\n\n.btn-primary[_ngcontent-%COMP%]:active:not(:disabled) {\n background: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-ghost[_ngcontent-%COMP%] {\n background: transparent;\n color: var(--mj-text-muted);\n border: none;\n}\n\n.btn-ghost[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n}\n\n.btn-ghost[_ngcontent-%COMP%]:active:not(:disabled) {\n background: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-danger[_ngcontent-%COMP%] {\n background: var(--mj-status-error);\n color: var(--mj-bg-surface);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.btn-danger[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-status-error);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n}\n\n.btn-danger[_ngcontent-%COMP%]:active:not(:disabled) {\n background: var(--mj-status-error);\n transform: scale(0.98);\n}\n\n.btn-sm[_ngcontent-%COMP%] {\n padding: 0.5rem 0.875rem;\n font-size: 0.8125rem;\n min-height: 36px;\n}\n\n\n\n\n\n.badge-system[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n border: 1px solid var(--mj-status-warning);\n}\n\n.badge-custom[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n border: 1px solid var(--mj-status-success);\n}\n\n\n\n\n\nbutton[_ngcontent-%COMP%]:focus-visible, \ninput[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n\n\n\n\n@media (prefers-reduced-motion: reduce) {\n *[_ngcontent-%COMP%], \n *[_ngcontent-%COMP%]::before, \n *[_ngcontent-%COMP%]::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n\n\n\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n\n\n\n\n@media (max-width: 639px) {\n .notification-preferences-container[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .notification-types-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n gap: 1rem;\n }\n\n .card-header[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .card-body[_ngcontent-%COMP%] {\n padding: 1rem;\n }\n\n .delivery-checkboxes[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .channel-checkbox[_ngcontent-%COMP%] {\n width: 100%;\n }\n\n .actions[_ngcontent-%COMP%] {\n flex-direction: column;\n padding: 1rem;\n }\n\n .actions[_ngcontent-%COMP%] .btn[_ngcontent-%COMP%] {\n width: 100%;\n justify-content: center;\n }\n}\n\n@media (min-width: 640px) and (max-width: 767px) {\n .notification-types-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n}\n\n@media (min-width: 768px) {\n .notification-preferences-container[_ngcontent-%COMP%] {\n padding: 2rem;\n }\n\n .notification-types-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));\n }\n}\n\n@media (min-width: 1024px) {\n .notification-types-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));\n gap: 1.5rem;\n }\n}"] });
|
|
379
374
|
}
|
|
380
375
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(NotificationPreferencesComponent, [{
|
|
381
376
|
type: Component,
|
|
382
|
-
args: [{ standalone: false, selector: 'mj-notification-preferences', template: "<div class=\"notification-preferences-container\">\n @if (loading) {\n <mj-loading text=\"Loading notification preferences...\"></mj-loading>\n } @else {\n <div class=\"content\">\n @if (viewModels.length === 0) {\n <!-- Empty state -->\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-bell-slash\"></i>\n <p>No notification types configured yet.</p>\n </div>\n } @else {\n <!-- Notification type cards -->\n <div class=\"notification-types-grid\">\n @for (vm of viewModels; track vm.type.ID) {\n <article class=\"notification-type-card\"\n [class.changed]=\"vm.changed\"\n role=\"region\"\n [attr.aria-label]=\"'Notification settings for ' + vm.type.Name\">\n\n <div class=\"card-header\" [style.border-left-color]=\"getTypeColor(vm.type)\">\n <div class=\"icon-wrapper\" [style.background-color]=\"getTypeColor(vm.type) + '20'\">\n <i [class]=\"'fa-solid ' + getTypeIcon(vm.type)\" [style.color]=\"getTypeColor(vm.type)\" [attr.aria-hidden]=\"true\"></i>\n </div>\n <div class=\"header-text\">\n <h4>{{ vm.type.Name }}</h4>\n <p class=\"type-description\">{{ vm.type.Description }}</p>\n </div>\n </div>\n\n <div class=\"card-body\">\n <!-- Delivery channel checkboxes -->\n <div class=\"preference-row\">\n <label class=\"delivery-label\" [attr.id]=\"'delivery-label-' + vm.type.ID\">Delivery Channels</label>\n\n <div class=\"delivery-checkboxes\" role=\"group\" [attr.aria-labelledby]=\"'delivery-label-' + vm.type.ID\">\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.inAppEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable in-app notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-bell\" aria-hidden=\"true\"></i>\n <span>In-App</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.emailEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable email notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-envelope\" aria-hidden=\"true\"></i>\n <span>Email</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.smsEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable SMS notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-mobile\" aria-hidden=\"true\"></i>\n <span>SMS</span>\n </label>\n </div>\n\n @if (!getAllowUserPreference(vm.type)) {\n <div class=\"info-message\" role=\"alert\" aria-live=\"polite\">\n <i class=\"fa-solid fa-info-circle\" aria-hidden=\"true\"></i>\n <span>This notification type's delivery channels cannot be customized.</span>\n </div>\n }\n </div>\n\n <!-- Auto-expire info -->\n @if (getTypeAutoExpireDays(vm.type)) {\n <div class=\"metadata-row\">\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n <span class=\"metadata-text\">\n Auto-marks as read after {{ getTypeAutoExpireDays(vm.type) }} day(s)\n </span>\n </div>\n }\n </div>\n </article>\n }\n </div>\n }\n\n <!-- Action buttons -->\n @if (hasChanges) {\n <div class=\"actions\" role=\"group\" aria-label=\"Save or cancel changes\">\n <button class=\"btn btn-secondary\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n Cancel\n </button>\n <button class=\"btn btn-primary\"\n type=\"button\"\n (click)=\"save()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n @if (saving) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"fa-solid fa-save\" aria-hidden=\"true\"></i>\n }\n <span>{{ saving ? 'Saving...' : 'Save Preferences' }}</span>\n </button>\n </div>\n }\n </div>\n }\n</div>\n", styles: ["/* =============================================================================\n Notification Preferences Component\n Using MJ Design Tokens\n ============================================================================= */\n\n/* Host element configuration */\n:host {\n display: block;\n width: 100%;\n}\n\n/* -----------------------------------------------------------------------------\n Container & Layout\n ----------------------------------------------------------------------------- */\n.notification-preferences-container {\n padding: 1.5rem;\n max-width: 1200px;\n margin: 0 auto;\n background: var(--mj-bg-page);\n}\n\n/* -----------------------------------------------------------------------------\n Loading State\n ----------------------------------------------------------------------------- */\n.content {\n animation: fadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n/* -----------------------------------------------------------------------------\n Empty State\n ----------------------------------------------------------------------------- */\n.empty-state {\n text-align: center;\n padding: 3rem 1.5rem;\n}\n\n.empty-state i {\n font-size: 4rem;\n color: var(--mj-border-strong);\n margin-bottom: 1.5rem;\n display: block;\n}\n\n.empty-state p {\n font-size: 1.125rem;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Notification Types Grid\n ----------------------------------------------------------------------------- */\n.notification-types-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));\n gap: 1.25rem;\n margin-bottom: 1.5rem;\n}\n\n/* -----------------------------------------------------------------------------\n Notification Type Card\n ----------------------------------------------------------------------------- */\n.notification-type-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-radius: 12px;\n border-left: 4px solid var(--mj-border-strong);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.notification-type-card:hover {\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n border-color: var(--mj-brand-primary);\n}\n\n.notification-type-card.changed {\n border-left-color: var(--mj-brand-primary) !important;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.08);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n/* -----------------------------------------------------------------------------\n Card Header\n ----------------------------------------------------------------------------- */\n.card-header {\n display: flex;\n gap: 1rem;\n padding: 1.25rem;\n border-bottom: 1px solid var(--mj-border-strong);\n align-items: flex-start;\n}\n\n.icon-wrapper {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: transform 0.2s;\n}\n\n.notification-type-card:hover .icon-wrapper {\n transform: scale(1.05);\n}\n\n.icon-wrapper i {\n font-size: 1.5rem;\n}\n\n.header-text {\n flex: 1;\n min-width: 0;\n}\n\n.header-text h4 {\n margin: 0 0 0.375rem 0;\n font-size: 1.125rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.type-description {\n margin: 0;\n font-size: 0.875rem;\n color: var(--mj-text-muted);\n line-height: 1.4;\n}\n\n/* -----------------------------------------------------------------------------\n Card Body\n ----------------------------------------------------------------------------- */\n.card-body {\n padding: 1.25rem;\n}\n\n.preference-row {\n margin-bottom: 1rem;\n}\n\n.preference-row:last-child {\n margin-bottom: 0;\n}\n\n.delivery-label {\n display: block;\n font-weight: 600;\n margin-bottom: 0.75rem;\n font-size: 0.875rem;\n color: var(--mj-text-primary);\n letter-spacing: 0.01em;\n}\n\n/* -----------------------------------------------------------------------------\n Delivery Channel Checkboxes\n ----------------------------------------------------------------------------- */\n.delivery-checkboxes {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n}\n\n.channel-checkbox {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n padding: 0.75rem 1rem;\n border: 2px solid var(--mj-border-strong);\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n user-select: none;\n background: var(--mj-bg-page);\n min-height: 44px;\n}\n\n.channel-checkbox input[type=\"checkbox\"] {\n width: 18px;\n height: 18px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n margin: 0;\n}\n\n.channel-checkbox input[type=\"checkbox\"]:disabled {\n cursor: not-allowed;\n}\n\n.channel-checkbox i {\n color: var(--mj-text-muted);\n font-size: 1rem;\n transition: color 0.2s;\n}\n\n.channel-checkbox span {\n font-size: 0.875rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.channel-checkbox:hover:not(:has(input:disabled)) {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.channel-checkbox:hover:not(:has(input:disabled)) i {\n color: var(--mj-brand-primary);\n}\n\n.channel-checkbox:has(input:checked) {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.channel-checkbox:has(input:checked) i {\n color: var(--mj-brand-primary);\n}\n\n.channel-checkbox:has(input:disabled) {\n opacity: 0.5;\n cursor: not-allowed;\n background: var(--mj-bg-surface-sunken);\n}\n\n.channel-checkbox:focus-within {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Info Message\n ----------------------------------------------------------------------------- */\n.info-message {\n margin-top: 0.75rem;\n padding: 0.75rem;\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n border-left: 3px solid var(--mj-status-warning);\n border-radius: 8px;\n font-size: 0.8125rem;\n display: flex;\n gap: 0.75rem;\n align-items: flex-start;\n line-height: 1.4;\n color: var(--mj-text-primary);\n}\n\n.info-message i {\n color: var(--mj-status-warning);\n font-size: 1rem;\n margin-top: 0.1rem;\n flex-shrink: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Metadata Row\n ----------------------------------------------------------------------------- */\n.metadata-row {\n display: flex;\n gap: 0.625rem;\n align-items: center;\n font-size: 0.8125rem;\n color: var(--mj-text-muted);\n padding-top: 0.75rem;\n margin-top: 0.75rem;\n border-top: 1px solid var(--mj-border-strong);\n}\n\n.metadata-row i {\n color: var(--mj-text-muted);\n font-size: 0.875rem;\n flex-shrink: 0;\n}\n\n.metadata-text {\n line-height: 1.4;\n}\n\n/* -----------------------------------------------------------------------------\n Action Buttons Section\n ----------------------------------------------------------------------------- */\n.actions {\n display: flex;\n justify-content: flex-start;\n gap: 0.75rem;\n padding: 1.25rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: 12px;\n border: 1px solid var(--mj-border-strong);\n margin-top: 1.5rem;\n}\n\n/* -----------------------------------------------------------------------------\n Button System\n ----------------------------------------------------------------------------- */\n.btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.75rem 1.5rem;\n border: none;\n border-radius: 9999px;\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n white-space: nowrap;\n min-height: 44px;\n}\n\n.btn:disabled {\n opacity: 0.38;\n cursor: not-allowed;\n}\n\n.btn:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n.btn i {\n font-size: 1rem;\n}\n\n.btn-secondary {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n border: 1px solid var(--mj-border-strong);\n}\n\n.btn-secondary:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n border-color: var(--mj-brand-primary);\n}\n\n.btn-secondary:active:not(:disabled) {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-primary {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.btn-primary:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n}\n\n.btn-primary:active:not(:disabled) {\n background: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-ghost {\n background: transparent;\n color: var(--mj-text-muted);\n border: none;\n}\n\n.btn-ghost:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n}\n\n.btn-ghost:active:not(:disabled) {\n background: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-danger {\n background: var(--mj-status-error);\n color: var(--mj-bg-surface);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.btn-danger:hover:not(:disabled) {\n background: var(--mj-status-error);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n}\n\n.btn-danger:active:not(:disabled) {\n background: var(--mj-status-error);\n transform: scale(0.98);\n}\n\n.btn-sm {\n padding: 0.5rem 0.875rem;\n font-size: 0.8125rem;\n min-height: 36px;\n}\n\n/* -----------------------------------------------------------------------------\n Badge System\n ----------------------------------------------------------------------------- */\n.badge-system {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n border: 1px solid var(--mj-status-warning);\n}\n\n.badge-custom {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n border: 1px solid var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Accessibility: Focus Indicators\n ----------------------------------------------------------------------------- */\nbutton:focus-visible,\ninput:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Accessibility: Reduced Motion\n ----------------------------------------------------------------------------- */\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Animations\n ----------------------------------------------------------------------------- */\n@keyframes fadeIn {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* -----------------------------------------------------------------------------\n Responsive Design - Mobile First\n ----------------------------------------------------------------------------- */\n@media (max-width: 639px) {\n .notification-preferences-container {\n padding: 1rem;\n }\n\n .notification-types-grid {\n grid-template-columns: 1fr;\n gap: 1rem;\n }\n\n .card-header {\n padding: 1rem;\n }\n\n .card-body {\n padding: 1rem;\n }\n\n .delivery-checkboxes {\n flex-direction: column;\n }\n\n .channel-checkbox {\n width: 100%;\n }\n\n .actions {\n flex-direction: column;\n padding: 1rem;\n }\n\n .actions .btn {\n width: 100%;\n justify-content: center;\n }\n}\n\n@media (min-width: 640px) and (max-width: 767px) {\n .notification-types-grid {\n grid-template-columns: 1fr;\n }\n}\n\n@media (min-width: 768px) {\n .notification-preferences-container {\n padding: 2rem;\n }\n\n .notification-types-grid {\n grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));\n }\n}\n\n@media (min-width: 1024px) {\n .notification-types-grid {\n grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));\n gap: 1.5rem;\n }\n}\n"] }]
|
|
377
|
+
args: [{ standalone: false, selector: 'mj-notification-preferences', template: "<div class=\"notification-preferences-container\">\n @if (loading) {\n <mj-loading text=\"Loading notification preferences...\"></mj-loading>\n } @else {\n <div class=\"content\">\n @if (viewModels.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-bell-slash\"\n Title=\"No notification types configured yet.\" />\n } @else {\n <!-- Notification type cards -->\n <div class=\"notification-types-grid\">\n @for (vm of viewModels; track vm.type.ID) {\n <article class=\"notification-type-card\"\n [class.changed]=\"vm.changed\"\n role=\"region\"\n [attr.aria-label]=\"'Notification settings for ' + vm.type.Name\">\n\n <div class=\"card-header\" [style.border-left-color]=\"getTypeColor(vm.type)\">\n <div class=\"icon-wrapper\" [style.background-color]=\"getTypeColor(vm.type) + '20'\">\n <i [class]=\"'fa-solid ' + getTypeIcon(vm.type)\" [style.color]=\"getTypeColor(vm.type)\" [attr.aria-hidden]=\"true\"></i>\n </div>\n <div class=\"header-text\">\n <h4>{{ vm.type.Name }}</h4>\n <p class=\"type-description\">{{ vm.type.Description }}</p>\n </div>\n </div>\n\n <div class=\"card-body\">\n <!-- Delivery channel checkboxes -->\n <div class=\"preference-row\">\n <label class=\"delivery-label\" [attr.id]=\"'delivery-label-' + vm.type.ID\">Delivery Channels</label>\n\n <div class=\"delivery-checkboxes\" role=\"group\" [attr.aria-labelledby]=\"'delivery-label-' + vm.type.ID\">\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.inAppEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable in-app notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-bell\" aria-hidden=\"true\"></i>\n <span>In-App</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.emailEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable email notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-envelope\" aria-hidden=\"true\"></i>\n <span>Email</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.smsEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable SMS notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-mobile\" aria-hidden=\"true\"></i>\n <span>SMS</span>\n </label>\n </div>\n\n @if (!getAllowUserPreference(vm.type)) {\n <mj-alert Variant=\"info\" class=\"info-message\">This notification type's delivery channels cannot be customized.</mj-alert>\n }\n </div>\n\n <!-- Auto-expire info -->\n @if (getTypeAutoExpireDays(vm.type)) {\n <div class=\"metadata-row\">\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n <span class=\"metadata-text\">\n Auto-marks as read after {{ getTypeAutoExpireDays(vm.type) }} day(s)\n </span>\n </div>\n }\n </div>\n </article>\n }\n </div>\n }\n\n <!-- Action buttons -->\n @if (hasChanges) {\n <div class=\"actions\" role=\"group\" aria-label=\"Save or cancel changes\">\n <button class=\"btn btn-secondary\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n Cancel\n </button>\n <button class=\"btn btn-primary\"\n type=\"button\"\n (click)=\"save()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n @if (saving) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"fa-solid fa-save\" aria-hidden=\"true\"></i>\n }\n <span>{{ saving ? 'Saving...' : 'Save Preferences' }}</span>\n </button>\n </div>\n }\n </div>\n }\n</div>\n", styles: ["/* =============================================================================\n Notification Preferences Component\n Using MJ Design Tokens\n ============================================================================= */\n\n/* Host element configuration */\n:host {\n display: block;\n width: 100%;\n}\n\n/* -----------------------------------------------------------------------------\n Container & Layout\n ----------------------------------------------------------------------------- */\n.notification-preferences-container {\n padding: 1.5rem;\n max-width: 1200px;\n margin: 0 auto;\n background: var(--mj-bg-page);\n}\n\n/* -----------------------------------------------------------------------------\n Loading State\n ----------------------------------------------------------------------------- */\n.content {\n animation: fadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n\n/* -----------------------------------------------------------------------------\n Notification Types Grid\n ----------------------------------------------------------------------------- */\n.notification-types-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));\n gap: 1.25rem;\n margin-bottom: 1.5rem;\n}\n\n/* -----------------------------------------------------------------------------\n Notification Type Card\n ----------------------------------------------------------------------------- */\n.notification-type-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-radius: 12px;\n border-left: 4px solid var(--mj-border-strong);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.notification-type-card:hover {\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n border-color: var(--mj-brand-primary);\n}\n\n.notification-type-card.changed {\n border-left-color: var(--mj-brand-primary) !important;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.08);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n/* -----------------------------------------------------------------------------\n Card Header\n ----------------------------------------------------------------------------- */\n.card-header {\n display: flex;\n gap: 1rem;\n padding: 1.25rem;\n border-bottom: 1px solid var(--mj-border-strong);\n align-items: flex-start;\n}\n\n.icon-wrapper {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: transform 0.2s;\n}\n\n.notification-type-card:hover .icon-wrapper {\n transform: scale(1.05);\n}\n\n.icon-wrapper i {\n font-size: 1.5rem;\n}\n\n.header-text {\n flex: 1;\n min-width: 0;\n}\n\n.header-text h4 {\n margin: 0 0 0.375rem 0;\n font-size: 1.125rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.type-description {\n margin: 0;\n font-size: 0.875rem;\n color: var(--mj-text-muted);\n line-height: 1.4;\n}\n\n/* -----------------------------------------------------------------------------\n Card Body\n ----------------------------------------------------------------------------- */\n.card-body {\n padding: 1.25rem;\n}\n\n.preference-row {\n margin-bottom: 1rem;\n}\n\n.preference-row:last-child {\n margin-bottom: 0;\n}\n\n.delivery-label {\n display: block;\n font-weight: 600;\n margin-bottom: 0.75rem;\n font-size: 0.875rem;\n color: var(--mj-text-primary);\n letter-spacing: 0.01em;\n}\n\n/* -----------------------------------------------------------------------------\n Delivery Channel Checkboxes\n ----------------------------------------------------------------------------- */\n.delivery-checkboxes {\n display: flex;\n gap: 0.75rem;\n flex-wrap: wrap;\n}\n\n.channel-checkbox {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n padding: 0.75rem 1rem;\n border: 2px solid var(--mj-border-strong);\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n user-select: none;\n background: var(--mj-bg-page);\n min-height: 44px;\n}\n\n.channel-checkbox input[type=\"checkbox\"] {\n width: 18px;\n height: 18px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n margin: 0;\n}\n\n.channel-checkbox input[type=\"checkbox\"]:disabled {\n cursor: not-allowed;\n}\n\n.channel-checkbox i {\n color: var(--mj-text-muted);\n font-size: 1rem;\n transition: color 0.2s;\n}\n\n.channel-checkbox span {\n font-size: 0.875rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.channel-checkbox:hover:not(:has(input:disabled)) {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.channel-checkbox:hover:not(:has(input:disabled)) i {\n color: var(--mj-brand-primary);\n}\n\n.channel-checkbox:has(input:checked) {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.channel-checkbox:has(input:checked) i {\n color: var(--mj-brand-primary);\n}\n\n.channel-checkbox:has(input:disabled) {\n opacity: 0.5;\n cursor: not-allowed;\n background: var(--mj-bg-surface-sunken);\n}\n\n.channel-checkbox:focus-within {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Info Message \u2014 positions the info mj-alert (component is margin-less but for bottom)\n ----------------------------------------------------------------------------- */\n.info-message { margin-top: 0.75rem; }\n\n/* -----------------------------------------------------------------------------\n Metadata Row\n ----------------------------------------------------------------------------- */\n.metadata-row {\n display: flex;\n gap: 0.625rem;\n align-items: center;\n font-size: 0.8125rem;\n color: var(--mj-text-muted);\n padding-top: 0.75rem;\n margin-top: 0.75rem;\n border-top: 1px solid var(--mj-border-strong);\n}\n\n.metadata-row i {\n color: var(--mj-text-muted);\n font-size: 0.875rem;\n flex-shrink: 0;\n}\n\n.metadata-text {\n line-height: 1.4;\n}\n\n/* -----------------------------------------------------------------------------\n Action Buttons Section\n ----------------------------------------------------------------------------- */\n.actions {\n display: flex;\n justify-content: flex-start;\n gap: 0.75rem;\n padding: 1.25rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: 12px;\n border: 1px solid var(--mj-border-strong);\n margin-top: 1.5rem;\n}\n\n/* -----------------------------------------------------------------------------\n Button System\n ----------------------------------------------------------------------------- */\n.btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.75rem 1.5rem;\n border: none;\n border-radius: 9999px;\n font-size: 0.875rem;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n white-space: nowrap;\n min-height: 44px;\n}\n\n.btn:disabled {\n opacity: 0.38;\n cursor: not-allowed;\n}\n\n.btn:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n.btn i {\n font-size: 1rem;\n}\n\n.btn-secondary {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n border: 1px solid var(--mj-border-strong);\n}\n\n.btn-secondary:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n border-color: var(--mj-brand-primary);\n}\n\n.btn-secondary:active:not(:disabled) {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-primary {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.btn-primary:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n}\n\n.btn-primary:active:not(:disabled) {\n background: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-ghost {\n background: transparent;\n color: var(--mj-text-muted);\n border: none;\n}\n\n.btn-ghost:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-bg-surface);\n}\n\n.btn-ghost:active:not(:disabled) {\n background: var(--mj-brand-primary);\n transform: scale(0.98);\n}\n\n.btn-danger {\n background: var(--mj-status-error);\n color: var(--mj-bg-surface);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.btn-danger:hover:not(:disabled) {\n background: var(--mj-status-error);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.08);\n}\n\n.btn-danger:active:not(:disabled) {\n background: var(--mj-status-error);\n transform: scale(0.98);\n}\n\n.btn-sm {\n padding: 0.5rem 0.875rem;\n font-size: 0.8125rem;\n min-height: 36px;\n}\n\n/* -----------------------------------------------------------------------------\n Badge System\n ----------------------------------------------------------------------------- */\n.badge-system {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n border: 1px solid var(--mj-status-warning);\n}\n\n.badge-custom {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.375rem 0.875rem;\n border-radius: 9999px;\n font-size: 0.75rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.03em;\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n border: 1px solid var(--mj-status-success);\n}\n\n/* -----------------------------------------------------------------------------\n Accessibility: Focus Indicators\n ----------------------------------------------------------------------------- */\nbutton:focus-visible,\ninput:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Accessibility: Reduced Motion\n ----------------------------------------------------------------------------- */\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Animations\n ----------------------------------------------------------------------------- */\n@keyframes fadeIn {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* -----------------------------------------------------------------------------\n Responsive Design - Mobile First\n ----------------------------------------------------------------------------- */\n@media (max-width: 639px) {\n .notification-preferences-container {\n padding: 1rem;\n }\n\n .notification-types-grid {\n grid-template-columns: 1fr;\n gap: 1rem;\n }\n\n .card-header {\n padding: 1rem;\n }\n\n .card-body {\n padding: 1rem;\n }\n\n .delivery-checkboxes {\n flex-direction: column;\n }\n\n .channel-checkbox {\n width: 100%;\n }\n\n .actions {\n flex-direction: column;\n padding: 1rem;\n }\n\n .actions .btn {\n width: 100%;\n justify-content: center;\n }\n}\n\n@media (min-width: 640px) and (max-width: 767px) {\n .notification-types-grid {\n grid-template-columns: 1fr;\n }\n}\n\n@media (min-width: 768px) {\n .notification-preferences-container {\n padding: 2rem;\n }\n\n .notification-types-grid {\n grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));\n }\n}\n\n@media (min-width: 1024px) {\n .notification-types-grid {\n grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));\n gap: 1.5rem;\n }\n}\n"] }]
|
|
383
378
|
}], () => [{ type: i1.SharedService }], null); })();
|
|
384
379
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(NotificationPreferencesComponent, { className: "NotificationPreferencesComponent", filePath: "src/lib/notification-preferences/notification-preferences.component.ts", lineNumber: 53 }); })();
|
|
385
380
|
//# sourceMappingURL=notification-preferences.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-preferences.component.js","sourceRoot":"","sources":["../../../src/lib/notification-preferences/notification-preferences.component.ts","../../../src/lib/notification-preferences/notification-preferences.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAU,MAAM,eAAe,CAAC;AAElD,OAAO,EAAoE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAEjI,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;;;;;;ICJ7D,gCAAoE;;;IAK5D,8BAAyB;IACrB,uBAAsC;IACtC,yBAAG;IAAA,qDAAqC;IAC5C,AAD4C,iBAAI,EAC1C;;;IA0DkB,+BAA0D;IACtD,wBAA0D;IAC1D,4BAAM;IAAA,gFAAgE;IAC1E,AAD0E,iBAAO,EAC3E;;;IAMV,+BAA0B;IACtB,wBAAoD;IACpD,gCAA4B;IACxB,YACJ;IACJ,AADI,iBAAO,EACL;;;;IAFE,eACJ;IADI,yGACJ;;;;IA5DR,AADJ,AALJ,kCAGyE,aAEM,cACW;IAC9E,oBAAoH;IACxH,iBAAM;IAEF,AADJ,+BAAyB,SACjB;IAAA,YAAkB;IAAA,iBAAK;IAC3B,6BAA4B;IAAA,YAAyB;IAE7D,AADI,AADyD,iBAAI,EACvD,EACJ;IAKE,AADJ,AAFJ,+BAAuB,eAES,iBACiD;IAAA,kCAAiB;IAAA,iBAAQ;IAI1F,AADJ,AADJ,gCAAsG,iBAClE,iBAKiD;IAHtE,wUAA6B;IAC7B,mPAAU,6BAAmB,KAAC;IAFrC,iBAI6E;IAC7E,yBAAmD;IACnD,6BAAM;IAAA,uBAAM;IAChB,AADgB,iBAAO,EACf;IAGJ,AADJ,kCAAgC,iBAKgD;IAHrE,wUAA6B;IAC7B,mPAAU,6BAAmB,KAAC;IAFrC,iBAI4E;IAC5E,yBAAuD;IACvD,6BAAM;IAAA,sBAAK;IACf,AADe,iBAAO,EACd;IAGJ,AADJ,kCAAgC,iBAK8C;IAHnE,oUAA2B;IAC3B,mPAAU,6BAAmB,KAAC;IAFrC,iBAI0E;IAC1E,yBAAqD;IACrD,6BAAM;IAAA,oBAAG;IAEjB,AADI,AADa,iBAAO,EACZ,EACN;IAEN,uIAAwC;IAM5C,iBAAM;IAGN,uIAAsC;IAS9C,AADI,iBAAM,EACA;;;;IArED,wCAA4B;;IAIR,cAAiD;IAAjD,oEAAiD;IAC5C,cAAuD;IAAvD,0EAAuD;IAC1E,cAA4C;IAA5C,2DAA4C;IAAC,wDAAqC;;IAGjF,eAAkB;IAAlB,qCAAkB;IACM,eAAyB;IAAzB,4CAAyB;IAOvB,eAA0C;;IAE1B,eAAuD;;IAGtF,eAA6B;IAA7B,kDAA6B;IAE7B,qEAA6C;;IAQ7C,eAA6B;IAA7B,kDAA6B;IAE7B,qEAA6C;;IAQ7C,eAA2B;IAA3B,gDAA2B;IAE3B,qEAA6C;;IAO5D,eAKC;IALD,sEAKC;IAIL,cAOC;IAPD,oEAOC;;;IAtEjB,8BAAqC;IACjC,qIAwEC;IACL,iBAAM;;;IAzEF,cAwEC;IAxED,gCAwEC;;;IAoBO,wBAA8D;;;IAE9D,wBAAmD;;;;IAf3D,AADJ,8BAAsE,iBAKhC;IAF1B,mNAAS,eAAQ,KAAC;IAGtB,wBACJ;IAAA,iBAAS;IACT,kCAIkC;IAF1B,mNAAS,aAAM,KAAC;IAKlB,AAFF,6HAAc,uGAEL;IAGT,4BAAM;IAAA,YAA+C;IAE7D,AADI,AADyD,iBAAO,EACvD,EACP;;;IAhBM,cAAmB;IAAnB,wCAAmB;;IAOnB,eAAmB;IAAnB,wCAAmB;;IAEvB,cAIC;IAJD,uCAIC;IACK,eAA+C;IAA/C,sEAA+C;;;IA1GrE,8BAAqB;IAOf,AANF,gHAA+B,0FAMtB;IAgFT,gHAAkB;IAuBtB,iBAAM;;;IA7GF,cAmFC;IAnFD,wDAmFC;IAGD,eAsBC;IAtBD,4CAsBC;;ADjFb;;;;;;;;;;;;;GAaG;AAOH,MAAM,OAAO,gCAAiC,SAAQ,oBAAoB;IAMpD;IALpB,OAAO,GAAG,IAAI,CAAC;IACf,MAAM,GAAG,KAAK,CAAC;IACf,UAAU,GAAsC,EAAE,CAAC;IACnD,UAAU,GAAG,KAAK,CAAC;IAEnB,YAAoB,aAA4B;QAC9C,KAAK,EAAE,CAAC;QADU,kBAAa,GAAb,aAAa,CAAe;IACtC,CAAC;IAEX;;OAEG;IACH,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,QAAQ;QACpB,IAAI,CAAC;YACH,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YAEpB,8DAA8D;YAC9D,8EAA8E;YAC9E,8EAA8E;YAE9E,iEAAiE;YACjE,MAAM,KAAK,GAAG,CAAC,GAAG,cAAc,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;gBACzE,MAAM,SAAS,GAAG,CAAC,CAAC,QAAQ,IAAI,GAAG,CAAC;gBACpC,MAAM,SAAS,GAAG,CAAC,CAAC,QAAQ,IAAI,GAAG,CAAC;gBACpC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;oBAC5B,OAAO,SAAS,GAAG,SAAS,CAAC;gBAC/B,CAAC;gBACD,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;YAEH,wEAAwE;YACxE,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,CAAC,uBAAuB,CAAC;YAE9D,qCAAqC;YACrC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;gBACnC,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,kBAAkB,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBAElF,qDAAqD;gBACrD,MAAM,YAAY,GAAG,YAAY,EAAE,YAAY,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC;gBAC7E,MAAM,YAAY,GAAG,YAAY,EAAE,YAAY,IAAI,IAAI,CAAC,YAAY,IAAI,KAAK,CAAC;gBAC9E,MAAM,UAAU,GAAG,YAAY,EAAE,UAAU,IAAI,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC;gBAExE,OAAO;oBACL,IAAI;oBACJ,UAAU,EAAE,YAAY,IAAI,IAAI;oBAChC,YAAY;oBACZ,YAAY;oBACZ,UAAU;oBACV,OAAO,EAAE,KAAK;oBACd,oBAAoB,EAAE,YAAY;oBAClC,oBAAoB,EAAE,YAAY;oBAClC,kBAAkB,EAAE,UAAU;iBAC/B,CAAC;YACJ,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACrB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YACzE,IAAI,CAAC,aAAa,CAAC,wBAAwB,CAAC,4CAA4C,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACpH,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,eAAe,CAAC,EAAmC;QACjD,EAAE,CAAC,OAAO,GAAG,EAAE,CAAC,YAAY,KAAK,EAAE,CAAC,oBAAoB,IAAI,EAAE,CAAC,YAAY,KAAK,EAAE,CAAC,oBAAoB,IAAI,EAAE,CAAC,UAAU,KAAK,EAAE,CAAC,kBAAkB,CAAC;QACnJ,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC3D,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,IAAI;QACR,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC;YAC9B,MAAM,WAAW,GAAG,EAAE,CAAC,WAAW,CAAC;YACnC,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,sBAAsB,EAAE,CAAC;YAErD,2EAA2E;YAC3E,6DAA6D;YAC7D,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1D,IAAI,IAAI,GAAG,EAAE,CAAC,UAAU,CAAC;gBAEzB,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,+BAA+B;oBAC/B,IAAI,GAAG,MAAM,EAAE,CAAC,eAAe,CAAqC,mCAAmC,CAAC,CAAC;oBACzG,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC;oBAC7B,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;oBACrC,EAAE,CAAC,UAAU,GAAG,IAAI,CAAC;gBACvB,CAAC;gBAED,sDAAsD;gBACtD,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC,YAAY,IAAI,EAAE,CAAC,YAAY,IAAI,EAAE,CAAC,UAAU,CAAC;gBAEnE,iCAAiC;gBACjC,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC;gBACpC,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC;gBACpC,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC;gBAChC,IAAI,CAAC,gBAAgB,GAAG,UAAU,CAAC;gBACnC,iEAAiE;gBACjE,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,CAAC;YAED,2EAA2E;YAC3E,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC;YAE1C,IAAI,OAAO,EAAE,CAAC;gBACZ,2FAA2F;gBAE3F,yBAAyB;gBACzB,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;oBAC7B,EAAE,CAAC,oBAAoB,GAAG,EAAE,CAAC,YAAY,CAAC;oBAC1C,EAAE,CAAC,oBAAoB,GAAG,EAAE,CAAC,YAAY,CAAC;oBAC1C,EAAE,CAAC,kBAAkB,GAAG,EAAE,CAAC,UAAU,CAAC;oBACtC,EAAE,CAAC,OAAO,GAAG,KAAK,CAAC;gBACrB,CAAC,CAAC,CAAC;gBAEH,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;gBACxB,IAAI,CAAC,aAAa,CAAC,wBAAwB,CAAC,6CAA6C,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;YAC9G,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAChD,CAAC;YAED,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACtB,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;YACpB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YACzE,IAAI,CAAC,aAAa,CAAC,wBAAwB,CAAC,+BAA+B,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACvG,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,MAAM;QACJ,iBAAiB;QACjB,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;YAC7B,EAAE,CAAC,YAAY,GAAG,EAAE,CAAC,oBAAoB,CAAC;YAC1C,EAAE,CAAC,YAAY,GAAG,EAAE,CAAC,oBAAoB,CAAC;YAC1C,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,kBAAkB,CAAC;YACtC,EAAE,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IAC1B,CAAC;IAED;;;;;OAKG;IACH,WAAW,CAAC,IAAkC;QAC5C,OAAO,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC;IAChC,CAAC;IAED;;;;;OAKG;IACH,YAAY,CAAC,IAAkC;QAC7C,OAAO,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC;IAC9B,CAAC;IAED;;;;;OAKG;IACH,qBAAqB,CAAC,IAAkC;QACtD,OAAO,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC;IACrC,CAAC;IAED;;;;;OAKG;IACH,sBAAsB,CAAC,IAAkC;QACvD,OAAO,IAAI,CAAC,mBAAmB,KAAK,KAAK,CAAC;IAC5C,CAAC;0HA5MU,gCAAgC;6DAAhC,gCAAgC;YCpD7C,8BAAgD;YAG1C,AAFF,yGAAe,4EAEN;YAiHb,iBAAM;;YAnHF,cAkHC;YAlHD,qCAkHC;;;iFD/DQ,gCAAgC;cAN5C,SAAS;6BACI,KAAK,YACP,6BAA6B;;kFAI5B,gCAAgC","sourcesContent":["import { Component, OnInit } from '@angular/core';\nimport { Metadata } from '@memberjunction/core';\nimport { MJUserNotificationTypeEntity, MJUserNotificationPreferenceEntity, UserInfoEngine } from '@memberjunction/core-entities';\nimport { SharedService } from '@memberjunction/ng-shared';\nimport { UUIDsEqual } from '@memberjunction/global';\n\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\n/**\n * View model for managing notification preferences in the UI.\n * Combines notification type definition with user's current preferences and change tracking.\n */\ninterface NotificationPreferenceViewModel {\n /** The notification type definition (read-only) */\n type: MJUserNotificationTypeEntity;\n /** User's existing preference record, or null if not yet set */\n preference: MJUserNotificationPreferenceEntity | null;\n /** Current state: In-app notifications enabled */\n inAppEnabled: boolean;\n /** Current state: Email notifications enabled */\n emailEnabled: boolean;\n /** Current state: SMS notifications enabled */\n smsEnabled: boolean;\n /** Whether this preference has been modified by the user */\n changed: boolean;\n /** Original value for rollback: In-app enabled state */\n originalInAppEnabled: boolean;\n /** Original value for rollback: Email enabled state */\n originalEmailEnabled: boolean;\n /** Original value for rollback: SMS enabled state */\n originalSmsEnabled: boolean;\n}\n\n/**\n * Component for managing user notification preferences.\n *\n * Displays notification types with configurable delivery channels (in-app, email, SMS).\n * Users can customize which channels they want to receive notifications through,\n * unless the notification type restricts user customization.\n *\n * Uses Material Design 3 (MD3) styling principles for consistent, accessible UI.\n *\n * Data flow:\n * - Loads notification types from UserInfoEngine (global cache)\n * - Loads user preferences from UserInfoEngine (per-user cache)\n * - Saves preferences using transaction groups for batch updates\n */\n@Component({\n standalone: false,\n selector: 'mj-notification-preferences',\n templateUrl: './notification-preferences.component.html',\n styleUrls: ['./notification-preferences.component.css'],\n})\nexport class NotificationPreferencesComponent extends BaseAngularComponent implements OnInit {\n loading = true;\n saving = false;\n viewModels: NotificationPreferenceViewModel[] = [];\n hasChanges = false;\n\n constructor(private sharedService: SharedService) {\n super();}\n\n /**\n * Angular lifecycle hook - initializes the component by loading notification preferences.\n */\n async ngOnInit(): Promise<void> {\n await this.loadData();\n }\n\n /**\n * Loads notification types and user preferences from UserInfoEngine.\n * Builds view models by merging type defaults with user preferences.\n * Sorts types by Priority (ascending), then Name (alphabetical).\n * @private\n */\n private async loadData(): Promise<void> {\n try {\n this.loading = true;\n\n // UserInfoEngine is auto-configured via @RegisterForStartup()\n // NotificationEngine (server-side) loads notification types into global cache\n // UserInfoEngine provides a getter to access them from both client and server\n\n // Get notification types from UserInfoEngine, sorted client-side\n const types = [...UserInfoEngine.Instance.NotificationTypes].sort((a, b) => {\n const priorityA = a.Priority ?? 999;\n const priorityB = b.Priority ?? 999;\n if (priorityA !== priorityB) {\n return priorityA - priorityB;\n }\n return a.Name.localeCompare(b.Name);\n });\n\n // Get preferences from UserInfoEngine (entity objects - can be mutated)\n const prefs = UserInfoEngine.Instance.NotificationPreferences;\n\n // Build view models from cached data\n this.viewModels = types.map((type) => {\n const existingPref = prefs.find((p) => UUIDsEqual(p.NotificationTypeID, type.ID));\n\n // Get channel values: user preference > type default\n const inAppEnabled = existingPref?.InAppEnabled ?? type.DefaultInApp ?? true;\n const emailEnabled = existingPref?.EmailEnabled ?? type.DefaultEmail ?? false;\n const smsEnabled = existingPref?.SMSEnabled ?? type.DefaultSMS ?? false;\n\n return {\n type,\n preference: existingPref || null,\n inAppEnabled,\n emailEnabled,\n smsEnabled,\n changed: false,\n originalInAppEnabled: inAppEnabled,\n originalEmailEnabled: emailEnabled,\n originalSmsEnabled: smsEnabled,\n };\n });\n\n this.loading = false;\n } catch (error: unknown) {\n this.loading = false;\n const message = error instanceof Error ? error.message : 'Unknown error';\n this.sharedService.CreateSimpleNotification(`Failed to load notification preferences: ${message}`, 'error', 3000);\n }\n }\n\n /**\n * Called when a user toggles any delivery channel checkbox.\n * Updates the view model's changed flag by comparing current state to original values.\n * Sets the global hasChanges flag to show/hide the save/cancel buttons.\n * @param vm The view model for the notification type being modified\n */\n onChannelChange(vm: NotificationPreferenceViewModel): void {\n vm.changed = vm.inAppEnabled !== vm.originalInAppEnabled || vm.emailEnabled !== vm.originalEmailEnabled || vm.smsEnabled !== vm.originalSmsEnabled;\n this.hasChanges = this.viewModels.some((v) => v.changed);\n }\n\n /**\n * Saves all changed notification preferences using a transaction group for batch updates.\n * Creates new preference records for types that don't have existing preferences.\n * Updates the Enabled field based on whether any channel is enabled.\n * Shows success/error notification on completion.\n * @returns Promise that resolves when save is complete\n */\n async save(): Promise<void> {\n try {\n this.saving = true;\n const md = this.ProviderToUse;\n const currentUser = md.CurrentUser;\n const transGroup = await md.CreateTransactionGroup();\n\n // Queue all saves in transaction group - no need to await individual saves\n // Transaction group queues them and submits all in one batch\n for (const vm of this.viewModels.filter((v) => v.changed)) {\n let pref = vm.preference;\n\n if (!pref) {\n // Create new preference record\n pref = await md.GetEntityObject<MJUserNotificationPreferenceEntity>('MJ: User Notification Preferences');\n pref.UserID = currentUser.ID;\n pref.NotificationTypeID = vm.type.ID;\n vm.preference = pref;\n }\n\n // Set Enabled based on whether any channel is enabled\n pref.Enabled = vm.inAppEnabled || vm.emailEnabled || vm.smsEnabled;\n\n // Set the boolean channel fields\n pref.InAppEnabled = vm.inAppEnabled;\n pref.EmailEnabled = vm.emailEnabled;\n pref.SMSEnabled = vm.smsEnabled;\n pref.TransactionGroup = transGroup;\n // Don't await - Save() with transaction group queues immediately\n pref.Save();\n }\n\n // Submit transaction group - this is where the actual network call happens\n const success = await transGroup.Submit();\n\n if (success) {\n // Cache refresh happens automatically in MJUserNotificationPreferenceEntityExtended.Save()\n\n // Update original values\n this.viewModels.forEach((vm) => {\n vm.originalInAppEnabled = vm.inAppEnabled;\n vm.originalEmailEnabled = vm.emailEnabled;\n vm.originalSmsEnabled = vm.smsEnabled;\n vm.changed = false;\n });\n\n this.hasChanges = false;\n this.sharedService.CreateSimpleNotification('Notification preferences saved successfully', 'success', 2500);\n } else {\n throw new Error('Failed to save preferences');\n }\n\n this.saving = false;\n } catch (error: unknown) {\n this.saving = false;\n const message = error instanceof Error ? error.message : 'Unknown error';\n this.sharedService.CreateSimpleNotification(`Failed to save preferences: ${message}`, 'error', 3000);\n }\n }\n\n /**\n * Cancels all unsaved changes and reverts to original values.\n * Resets the changed flag on all view models and hides the action buttons.\n */\n cancel(): void {\n // Revert changes\n this.viewModels.forEach((vm) => {\n vm.inAppEnabled = vm.originalInAppEnabled;\n vm.emailEnabled = vm.originalEmailEnabled;\n vm.smsEnabled = vm.originalSmsEnabled;\n vm.changed = false;\n });\n this.hasChanges = false;\n }\n\n /**\n * Gets the Font Awesome icon class for a notification type.\n * Used for MD3 dynamic icon styling in the card header.\n * @param type The notification type entity\n * @returns Font Awesome icon class (e.g., 'fa-bell'), defaults to 'fa-bell' if not specified\n */\n getTypeIcon(type: MJUserNotificationTypeEntity): string {\n return type.Icon || 'fa-bell';\n }\n\n /**\n * Gets the color hex code for a notification type.\n * Used for MD3 dynamic color styling (icon background, border accent).\n * @param type The notification type entity\n * @returns Hex color code (e.g., '#0076B6'), defaults to '#999' if not specified\n */\n getTypeColor(type: MJUserNotificationTypeEntity): string {\n return type.Color || '#999';\n }\n\n /**\n * Gets the auto-expire duration in days for a notification type.\n * Displayed in the metadata row to inform users about automatic read marking.\n * @param type The notification type entity\n * @returns Number of days until auto-expire, or null if not configured\n */\n getTypeAutoExpireDays(type: MJUserNotificationTypeEntity): number | null {\n return type.AutoExpireDays || null;\n }\n\n /**\n * Checks if users are allowed to customize preferences for this notification type.\n * When false, the delivery channel checkboxes are disabled and an info message is shown.\n * @param type The notification type entity\n * @returns True if user customization is allowed (default), false otherwise\n */\n getAllowUserPreference(type: MJUserNotificationTypeEntity): boolean {\n return type.AllowUserPreference !== false;\n }\n}\n","<div class=\"notification-preferences-container\">\n @if (loading) {\n <mj-loading text=\"Loading notification preferences...\"></mj-loading>\n } @else {\n <div class=\"content\">\n @if (viewModels.length === 0) {\n <!-- Empty state -->\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-bell-slash\"></i>\n <p>No notification types configured yet.</p>\n </div>\n } @else {\n <!-- Notification type cards -->\n <div class=\"notification-types-grid\">\n @for (vm of viewModels; track vm.type.ID) {\n <article class=\"notification-type-card\"\n [class.changed]=\"vm.changed\"\n role=\"region\"\n [attr.aria-label]=\"'Notification settings for ' + vm.type.Name\">\n\n <div class=\"card-header\" [style.border-left-color]=\"getTypeColor(vm.type)\">\n <div class=\"icon-wrapper\" [style.background-color]=\"getTypeColor(vm.type) + '20'\">\n <i [class]=\"'fa-solid ' + getTypeIcon(vm.type)\" [style.color]=\"getTypeColor(vm.type)\" [attr.aria-hidden]=\"true\"></i>\n </div>\n <div class=\"header-text\">\n <h4>{{ vm.type.Name }}</h4>\n <p class=\"type-description\">{{ vm.type.Description }}</p>\n </div>\n </div>\n\n <div class=\"card-body\">\n <!-- Delivery channel checkboxes -->\n <div class=\"preference-row\">\n <label class=\"delivery-label\" [attr.id]=\"'delivery-label-' + vm.type.ID\">Delivery Channels</label>\n\n <div class=\"delivery-checkboxes\" role=\"group\" [attr.aria-labelledby]=\"'delivery-label-' + vm.type.ID\">\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.inAppEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable in-app notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-bell\" aria-hidden=\"true\"></i>\n <span>In-App</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.emailEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable email notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-envelope\" aria-hidden=\"true\"></i>\n <span>Email</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.smsEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable SMS notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-mobile\" aria-hidden=\"true\"></i>\n <span>SMS</span>\n </label>\n </div>\n\n @if (!getAllowUserPreference(vm.type)) {\n <div class=\"info-message\" role=\"alert\" aria-live=\"polite\">\n <i class=\"fa-solid fa-info-circle\" aria-hidden=\"true\"></i>\n <span>This notification type's delivery channels cannot be customized.</span>\n </div>\n }\n </div>\n\n <!-- Auto-expire info -->\n @if (getTypeAutoExpireDays(vm.type)) {\n <div class=\"metadata-row\">\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n <span class=\"metadata-text\">\n Auto-marks as read after {{ getTypeAutoExpireDays(vm.type) }} day(s)\n </span>\n </div>\n }\n </div>\n </article>\n }\n </div>\n }\n\n <!-- Action buttons -->\n @if (hasChanges) {\n <div class=\"actions\" role=\"group\" aria-label=\"Save or cancel changes\">\n <button class=\"btn btn-secondary\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n Cancel\n </button>\n <button class=\"btn btn-primary\"\n type=\"button\"\n (click)=\"save()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n @if (saving) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"fa-solid fa-save\" aria-hidden=\"true\"></i>\n }\n <span>{{ saving ? 'Saving...' : 'Save Preferences' }}</span>\n </button>\n </div>\n }\n </div>\n }\n</div>\n"]}
|
|
1
|
+
{"version":3,"file":"notification-preferences.component.js","sourceRoot":"","sources":["../../../src/lib/notification-preferences/notification-preferences.component.ts","../../../src/lib/notification-preferences/notification-preferences.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, OnInit } from '@angular/core';\nimport { Metadata } from '@memberjunction/core';\nimport { MJUserNotificationTypeEntity, MJUserNotificationPreferenceEntity, UserInfoEngine } from '@memberjunction/core-entities';\nimport { SharedService } from '@memberjunction/ng-shared';\nimport { UUIDsEqual } from '@memberjunction/global';\n\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\n/**\n * View model for managing notification preferences in the UI.\n * Combines notification type definition with user's current preferences and change tracking.\n */\ninterface NotificationPreferenceViewModel {\n /** The notification type definition (read-only) */\n type: MJUserNotificationTypeEntity;\n /** User's existing preference record, or null if not yet set */\n preference: MJUserNotificationPreferenceEntity | null;\n /** Current state: In-app notifications enabled */\n inAppEnabled: boolean;\n /** Current state: Email notifications enabled */\n emailEnabled: boolean;\n /** Current state: SMS notifications enabled */\n smsEnabled: boolean;\n /** Whether this preference has been modified by the user */\n changed: boolean;\n /** Original value for rollback: In-app enabled state */\n originalInAppEnabled: boolean;\n /** Original value for rollback: Email enabled state */\n originalEmailEnabled: boolean;\n /** Original value for rollback: SMS enabled state */\n originalSmsEnabled: boolean;\n}\n\n/**\n * Component for managing user notification preferences.\n *\n * Displays notification types with configurable delivery channels (in-app, email, SMS).\n * Users can customize which channels they want to receive notifications through,\n * unless the notification type restricts user customization.\n *\n * Uses Material Design 3 (MD3) styling principles for consistent, accessible UI.\n *\n * Data flow:\n * - Loads notification types from UserInfoEngine (global cache)\n * - Loads user preferences from UserInfoEngine (per-user cache)\n * - Saves preferences using transaction groups for batch updates\n */\n@Component({\n standalone: false,\n selector: 'mj-notification-preferences',\n templateUrl: './notification-preferences.component.html',\n styleUrls: ['./notification-preferences.component.css'],\n})\nexport class NotificationPreferencesComponent extends BaseAngularComponent implements OnInit {\n loading = true;\n saving = false;\n viewModels: NotificationPreferenceViewModel[] = [];\n hasChanges = false;\n\n constructor(private sharedService: SharedService) {\n super();}\n\n /**\n * Angular lifecycle hook - initializes the component by loading notification preferences.\n */\n async ngOnInit(): Promise<void> {\n await this.loadData();\n }\n\n /**\n * Loads notification types and user preferences from UserInfoEngine.\n * Builds view models by merging type defaults with user preferences.\n * Sorts types by Priority (ascending), then Name (alphabetical).\n * @private\n */\n private async loadData(): Promise<void> {\n try {\n this.loading = true;\n\n // UserInfoEngine is auto-configured via @RegisterForStartup()\n // NotificationEngine (server-side) loads notification types into global cache\n // UserInfoEngine provides a getter to access them from both client and server\n\n // Get notification types from UserInfoEngine, sorted client-side\n const types = [...UserInfoEngine.Instance.NotificationTypes].sort((a, b) => {\n const priorityA = a.Priority ?? 999;\n const priorityB = b.Priority ?? 999;\n if (priorityA !== priorityB) {\n return priorityA - priorityB;\n }\n return a.Name.localeCompare(b.Name);\n });\n\n // Get preferences from UserInfoEngine (entity objects - can be mutated)\n const prefs = UserInfoEngine.Instance.NotificationPreferences;\n\n // Build view models from cached data\n this.viewModels = types.map((type) => {\n const existingPref = prefs.find((p) => UUIDsEqual(p.NotificationTypeID, type.ID));\n\n // Get channel values: user preference > type default\n const inAppEnabled = existingPref?.InAppEnabled ?? type.DefaultInApp ?? true;\n const emailEnabled = existingPref?.EmailEnabled ?? type.DefaultEmail ?? false;\n const smsEnabled = existingPref?.SMSEnabled ?? type.DefaultSMS ?? false;\n\n return {\n type,\n preference: existingPref || null,\n inAppEnabled,\n emailEnabled,\n smsEnabled,\n changed: false,\n originalInAppEnabled: inAppEnabled,\n originalEmailEnabled: emailEnabled,\n originalSmsEnabled: smsEnabled,\n };\n });\n\n this.loading = false;\n } catch (error: unknown) {\n this.loading = false;\n const message = error instanceof Error ? error.message : 'Unknown error';\n this.sharedService.CreateSimpleNotification(`Failed to load notification preferences: ${message}`, 'error', 3000);\n }\n }\n\n /**\n * Called when a user toggles any delivery channel checkbox.\n * Updates the view model's changed flag by comparing current state to original values.\n * Sets the global hasChanges flag to show/hide the save/cancel buttons.\n * @param vm The view model for the notification type being modified\n */\n onChannelChange(vm: NotificationPreferenceViewModel): void {\n vm.changed = vm.inAppEnabled !== vm.originalInAppEnabled || vm.emailEnabled !== vm.originalEmailEnabled || vm.smsEnabled !== vm.originalSmsEnabled;\n this.hasChanges = this.viewModels.some((v) => v.changed);\n }\n\n /**\n * Saves all changed notification preferences using a transaction group for batch updates.\n * Creates new preference records for types that don't have existing preferences.\n * Updates the Enabled field based on whether any channel is enabled.\n * Shows success/error notification on completion.\n * @returns Promise that resolves when save is complete\n */\n async save(): Promise<void> {\n try {\n this.saving = true;\n const md = this.ProviderToUse;\n const currentUser = md.CurrentUser;\n const transGroup = await md.CreateTransactionGroup();\n\n // Queue all saves in transaction group - no need to await individual saves\n // Transaction group queues them and submits all in one batch\n for (const vm of this.viewModels.filter((v) => v.changed)) {\n let pref = vm.preference;\n\n if (!pref) {\n // Create new preference record\n pref = await md.GetEntityObject<MJUserNotificationPreferenceEntity>('MJ: User Notification Preferences');\n pref.UserID = currentUser.ID;\n pref.NotificationTypeID = vm.type.ID;\n vm.preference = pref;\n }\n\n // Set Enabled based on whether any channel is enabled\n pref.Enabled = vm.inAppEnabled || vm.emailEnabled || vm.smsEnabled;\n\n // Set the boolean channel fields\n pref.InAppEnabled = vm.inAppEnabled;\n pref.EmailEnabled = vm.emailEnabled;\n pref.SMSEnabled = vm.smsEnabled;\n pref.TransactionGroup = transGroup;\n // Don't await - Save() with transaction group queues immediately\n pref.Save();\n }\n\n // Submit transaction group - this is where the actual network call happens\n const success = await transGroup.Submit();\n\n if (success) {\n // Cache refresh happens automatically in MJUserNotificationPreferenceEntityExtended.Save()\n\n // Update original values\n this.viewModels.forEach((vm) => {\n vm.originalInAppEnabled = vm.inAppEnabled;\n vm.originalEmailEnabled = vm.emailEnabled;\n vm.originalSmsEnabled = vm.smsEnabled;\n vm.changed = false;\n });\n\n this.hasChanges = false;\n this.sharedService.CreateSimpleNotification('Notification preferences saved successfully', 'success', 2500);\n } else {\n throw new Error('Failed to save preferences');\n }\n\n this.saving = false;\n } catch (error: unknown) {\n this.saving = false;\n const message = error instanceof Error ? error.message : 'Unknown error';\n this.sharedService.CreateSimpleNotification(`Failed to save preferences: ${message}`, 'error', 3000);\n }\n }\n\n /**\n * Cancels all unsaved changes and reverts to original values.\n * Resets the changed flag on all view models and hides the action buttons.\n */\n cancel(): void {\n // Revert changes\n this.viewModels.forEach((vm) => {\n vm.inAppEnabled = vm.originalInAppEnabled;\n vm.emailEnabled = vm.originalEmailEnabled;\n vm.smsEnabled = vm.originalSmsEnabled;\n vm.changed = false;\n });\n this.hasChanges = false;\n }\n\n /**\n * Gets the Font Awesome icon class for a notification type.\n * Used for MD3 dynamic icon styling in the card header.\n * @param type The notification type entity\n * @returns Font Awesome icon class (e.g., 'fa-bell'), defaults to 'fa-bell' if not specified\n */\n getTypeIcon(type: MJUserNotificationTypeEntity): string {\n return type.Icon || 'fa-bell';\n }\n\n /**\n * Gets the color hex code for a notification type.\n * Used for MD3 dynamic color styling (icon background, border accent).\n * @param type The notification type entity\n * @returns Hex color code (e.g., '#0076B6'), defaults to '#999' if not specified\n */\n getTypeColor(type: MJUserNotificationTypeEntity): string {\n return type.Color || '#999';\n }\n\n /**\n * Gets the auto-expire duration in days for a notification type.\n * Displayed in the metadata row to inform users about automatic read marking.\n * @param type The notification type entity\n * @returns Number of days until auto-expire, or null if not configured\n */\n getTypeAutoExpireDays(type: MJUserNotificationTypeEntity): number | null {\n return type.AutoExpireDays || null;\n }\n\n /**\n * Checks if users are allowed to customize preferences for this notification type.\n * When false, the delivery channel checkboxes are disabled and an info message is shown.\n * @param type The notification type entity\n * @returns True if user customization is allowed (default), false otherwise\n */\n getAllowUserPreference(type: MJUserNotificationTypeEntity): boolean {\n return type.AllowUserPreference !== false;\n }\n}\n","<div class=\"notification-preferences-container\">\n @if (loading) {\n <mj-loading text=\"Loading notification preferences...\"></mj-loading>\n } @else {\n <div class=\"content\">\n @if (viewModels.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-bell-slash\"\n Title=\"No notification types configured yet.\" />\n } @else {\n <!-- Notification type cards -->\n <div class=\"notification-types-grid\">\n @for (vm of viewModels; track vm.type.ID) {\n <article class=\"notification-type-card\"\n [class.changed]=\"vm.changed\"\n role=\"region\"\n [attr.aria-label]=\"'Notification settings for ' + vm.type.Name\">\n\n <div class=\"card-header\" [style.border-left-color]=\"getTypeColor(vm.type)\">\n <div class=\"icon-wrapper\" [style.background-color]=\"getTypeColor(vm.type) + '20'\">\n <i [class]=\"'fa-solid ' + getTypeIcon(vm.type)\" [style.color]=\"getTypeColor(vm.type)\" [attr.aria-hidden]=\"true\"></i>\n </div>\n <div class=\"header-text\">\n <h4>{{ vm.type.Name }}</h4>\n <p class=\"type-description\">{{ vm.type.Description }}</p>\n </div>\n </div>\n\n <div class=\"card-body\">\n <!-- Delivery channel checkboxes -->\n <div class=\"preference-row\">\n <label class=\"delivery-label\" [attr.id]=\"'delivery-label-' + vm.type.ID\">Delivery Channels</label>\n\n <div class=\"delivery-checkboxes\" role=\"group\" [attr.aria-labelledby]=\"'delivery-label-' + vm.type.ID\">\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.inAppEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable in-app notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-bell\" aria-hidden=\"true\"></i>\n <span>In-App</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.emailEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable email notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-envelope\" aria-hidden=\"true\"></i>\n <span>Email</span>\n </label>\n\n <label class=\"channel-checkbox\">\n <input type=\"checkbox\"\n [(ngModel)]=\"vm.smsEnabled\"\n (change)=\"onChannelChange(vm)\"\n [disabled]=\"!getAllowUserPreference(vm.type)\"\n [attr.aria-label]=\"'Enable SMS notifications for ' + vm.type.Name\">\n <i class=\"fa-solid fa-mobile\" aria-hidden=\"true\"></i>\n <span>SMS</span>\n </label>\n </div>\n\n @if (!getAllowUserPreference(vm.type)) {\n <mj-alert Variant=\"info\" class=\"info-message\">This notification type's delivery channels cannot be customized.</mj-alert>\n }\n </div>\n\n <!-- Auto-expire info -->\n @if (getTypeAutoExpireDays(vm.type)) {\n <div class=\"metadata-row\">\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n <span class=\"metadata-text\">\n Auto-marks as read after {{ getTypeAutoExpireDays(vm.type) }} day(s)\n </span>\n </div>\n }\n </div>\n </article>\n }\n </div>\n }\n\n <!-- Action buttons -->\n @if (hasChanges) {\n <div class=\"actions\" role=\"group\" aria-label=\"Save or cancel changes\">\n <button class=\"btn btn-secondary\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n Cancel\n </button>\n <button class=\"btn btn-primary\"\n type=\"button\"\n (click)=\"save()\"\n [disabled]=\"saving\"\n [attr.aria-busy]=\"saving\">\n @if (saving) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"fa-solid fa-save\" aria-hidden=\"true\"></i>\n }\n <span>{{ saving ? 'Saving...' : 'Save Preferences' }}</span>\n </button>\n </div>\n }\n </div>\n }\n</div>\n"]}
|