diff --git a/PLAYBOOK.md b/PLAYBOOK.md index f9648b97f..44f75d2bd 100644 --- a/PLAYBOOK.md +++ b/PLAYBOOK.md @@ -243,6 +243,7 @@ ng g service services/MediaQuery --project=core --dry-run ng g service services/DeepLink --project=core --dry-run ng g service services/Feature --project=core --dry-run ng g service services/GoogleAnalytics --project=core --dry-run +ng g service PushNotification --project=core --dry-run # `material` module to encapulate material libs which is impoted into any `Lazy-loaded Feature Modules` that need material components ng g lib material --prefix=ngx --spec=false --tags=shared-module --unit-test-runner=jest --dry-run @@ -300,7 +301,6 @@ ng g lib Notifications --prefix=ngx --tags=public-module --publishable=true --un ng g component notifications --project=notifications --flat --dry-run ng g class notification --type=model --project=notifications --dry-run ng g service notifications --project=notifications --dry-run -ng g service PushNotification --project=notifications --dry-run # generate components for `Quickpanel` Module ng g lib Quickpanel --prefix=ngx --tags=private-module --unit-test-runner=jest @@ -403,7 +403,8 @@ ng g component containers/about --project=home ng g component components/rainbow --project=dashboard --dry-run ng g component containers/dashboardLayout --project=dashboard --dry-run ng g component containers/overview --project=dashboard --dry-run - +ng g component containers/profile --project=dashboard --dry-run +ng g component containers/settings --project=dashboard --dry-run # generate containers, components for `widgets` Module ng g component containers/wizdash --project=widgets --dry-run diff --git a/libs/auth/src/lib/oauth.init.ts b/libs/auth/src/lib/oauth.init.ts index 2d50b2b51..16ae32fe2 100644 --- a/libs/auth/src/lib/oauth.init.ts +++ b/libs/auth/src/lib/oauth.init.ts @@ -21,7 +21,8 @@ export function initializeAuth(oauthService: OAuthService, store: Store) { if (oauthService.hasValidAccessToken()) { // This is called when using ImplicitFlow or page reload, no effect for ROPC Flow console.log('hasValidAccessToken'); - const profile: any = oauthService.getIdentityClaims(); + // const profile: any = oauthService.getIdentityClaims(); + const profile: any = await oauthService.loadUserProfile(); store.dispatch(new LoginSuccess(profile)); } return true; // need to return. diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts index 5192ee63a..2262df917 100644 --- a/libs/core/src/index.ts +++ b/libs/core/src/index.ts @@ -1,4 +1,6 @@ export * from './lib/core.module'; +export * from './lib/state/preference.state'; +export * from './lib/state/app.state'; export { PageTitleService } from './lib/services/page-title.service'; export { ServiceWorkerService } from './lib/services/service-worker.service'; export { MediaQueryService } from './lib/services/media-query.service'; @@ -6,4 +8,5 @@ export { DeepLinkService } from './lib/services/deep-link.service'; export { RouterStateData} from './lib/state/custom-router-state.serializer'; export { FeatureService, BrowserFeatureKey, BrowserFeature } from './lib/services/feature.service'; export { GoogleAnalyticsService } from './lib/services/google-analytics.service'; +export { PushNotificationService } from './lib/services/push-notification.service'; export { WINDOW } from './lib/services/window.token'; diff --git a/libs/core/src/lib/core.module.ts b/libs/core/src/lib/core.module.ts index 9fa0fdf4f..c1dd92041 100644 --- a/libs/core/src/lib/core.module.ts +++ b/libs/core/src/lib/core.module.ts @@ -8,6 +8,7 @@ import { NgxsFormPluginModule } from '@ngxs/form-plugin'; import { NgxPageScrollModule } from 'ngx-page-scroll'; import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin'; import { NgxsRouterPluginModule, RouterStateSerializer } from '@ngxs/router-plugin'; +import { NgxsStoragePluginModule } from '@ngxs/storage-plugin'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { library } from '@fortawesome/fontawesome-svg-core'; import { faTwitter, faGithub, faGoogle } from '@fortawesome/free-brands-svg-icons'; @@ -19,6 +20,7 @@ import { environment } from '@env/environment'; import { EventBus } from './state/eventbus'; import { defaultMenu, demoMenu, adminMenu } from './menu-data'; import { PreferenceState } from './state/preference.state'; +import { AppState } from './state/app.state'; import { InMemoryDataService } from './services/in-memory-data.service'; import { ErrorInterceptor } from './interceptors/error.interceptor'; import { CustomRouterStateSerializer } from './state/custom-router-state.serializer'; @@ -45,7 +47,10 @@ library.add(faTwitter, faGithub, faGoogle); MatSnackBarModule, NgxPageScrollModule, NavigatorModule.forRoot(defaultMenu), - NgxsModule.forRoot([AuthState, MenuState, PreferenceState]), + NgxsModule.forRoot([AuthState, MenuState, PreferenceState, AppState]), + NgxsStoragePluginModule.forRoot({ + key: ['preference', 'app.installed'] + }), NgxsReduxDevtoolsPluginModule.forRoot({ disabled: environment.production, // Set to true for prod mode maxAge: 10, diff --git a/libs/core/src/lib/services/google-analytics.service.ts b/libs/core/src/lib/services/google-analytics.service.ts index dad364b61..8faa1c5af 100644 --- a/libs/core/src/lib/services/google-analytics.service.ts +++ b/libs/core/src/lib/services/google-analytics.service.ts @@ -6,6 +6,7 @@ export enum EventCategory { SideNav = 'sideNav', Outbound = 'outboundLink', Login = 'login', + Install = 'install', } export enum EventAction { diff --git a/libs/notifications/src/lib/push-notification.service.spec.ts b/libs/core/src/lib/services/push-notification.service.spec.ts similarity index 100% rename from libs/notifications/src/lib/push-notification.service.spec.ts rename to libs/core/src/lib/services/push-notification.service.spec.ts diff --git a/libs/notifications/src/lib/push-notification.service.ts b/libs/core/src/lib/services/push-notification.service.ts similarity index 93% rename from libs/notifications/src/lib/push-notification.service.ts rename to libs/core/src/lib/services/push-notification.service.ts index 10ab34441..d2cf0cd55 100644 --- a/libs/notifications/src/lib/push-notification.service.ts +++ b/libs/core/src/lib/services/push-notification.service.ts @@ -26,7 +26,7 @@ export class PushNotificationService { const subscription = await this.swPush.requestSubscription({ serverPublicKey: environment.webPush.publicVapidKey }); console.log('Push subscription endpoint: ', subscription.endpoint); this.pushSubscription = subscription; - // this.apiService.post('push/register', subscription).subscribe(); + // return this.apiService.post('push/register', subscription).subscribe(); } unregister(): Observable { diff --git a/libs/core/src/lib/services/service-worker.service.ts b/libs/core/src/lib/services/service-worker.service.ts index e5c5a02aa..4129133fe 100644 --- a/libs/core/src/lib/services/service-worker.service.ts +++ b/libs/core/src/lib/services/service-worker.service.ts @@ -10,6 +10,7 @@ import { MatSnackBar } from '@angular/material'; export class ServiceWorkerService { constructor(private swUpdate: SwUpdate, @Inject(WINDOW) private window: Window, private snackBar: MatSnackBar) {} + // TODO: move to appState/eventBus? checkSWUpdate(): void { if (environment.production) { // Subscribe new worker is available diff --git a/libs/core/src/lib/state/app.state.ts b/libs/core/src/lib/state/app.state.ts new file mode 100644 index 000000000..fe2923119 --- /dev/null +++ b/libs/core/src/lib/state/app.state.ts @@ -0,0 +1,85 @@ +import { Action, NgxsOnInit, Selector, State, StateContext } from '@ngxs/store'; +import { Inject } from '@angular/core'; +import { WINDOW } from '../services/window.token'; + +export interface BeforeInstallPromptEvent extends Event { + readonly platforms: string[]; // ["web", "android", "windows"] + readonly userChoice: Promise<'accepted' | 'dismissed'>; + prompt(): void; +} + +export class ChangeInstallStatus { + static readonly type = '[App] Change Install Status'; + constructor(public payload: boolean) {} +} +export class SetInstallPrompt { + static readonly type = '[App] Set Install Prompt'; + constructor(public payload: BeforeInstallPromptEvent) {} +} +export class Online { + static readonly type = '[App] Network Online'; +} +export class Offline { + static readonly type = '[App] Network Offline'; +} + +export interface AppStateModel { + online: boolean; + installPrompt: BeforeInstallPromptEvent; + installed: boolean; +} + +@State({ + name: 'app', + defaults: { + online: window.navigator.onLine, + installPrompt: null, + installed: false + }, +}) +export class AppState { + constructor() {} + + @Selector() + static online(state: AppStateModel) { + return state.online; + } + + @Selector() + static installPrompt(state: AppStateModel) { + return state.installPrompt; + } + + @Selector() + static installed(state: AppStateModel) { + return state.installed; + } + + @Action(SetInstallPrompt) + setInstallPrompt({ patchState }: StateContext, { payload }: SetInstallPrompt) { + patchState({ + installPrompt: payload, + }); + } + + @Action(ChangeInstallStatus) + changeInstallStatus({ patchState }: StateContext, { payload }: ChangeInstallStatus) { + patchState({ + installed: payload, + }); + } + + @Action(Online) + enableNotifications({ patchState }: StateContext) { + patchState({ + online: true, + }); + } + + @Action(Offline) + disableNotifications({ patchState }: StateContext) { + patchState({ + online: false, + }); + } +} diff --git a/libs/core/src/lib/state/eventbus.ts b/libs/core/src/lib/state/eventbus.ts index 03b906b49..7479fd17b 100644 --- a/libs/core/src/lib/state/eventbus.ts +++ b/libs/core/src/lib/state/eventbus.ts @@ -1,5 +1,5 @@ import { Actions, ofActionErrored, ofActionSuccessful, Store } from '@ngxs/store'; -import { Injectable } from '@angular/core'; +import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core'; import { Login, LoginSuccess } from '@ngx-starter-kit/auth'; import { AuthenticateWebSocket, @@ -9,30 +9,68 @@ import { WebSocketDisconnected, } from '@ngx-starter-kit/socketio-plugin'; import { RouterNavigation, RouterState } from '@ngxs/router-plugin'; -import { RouterStateData } from '@ngx-starter-kit/core'; import { distinctUntilChanged, filter, map } from 'rxjs/operators'; import { PageTitleService } from '../services/page-title.service'; -import { GoogleAnalyticsService } from '../services/google-analytics.service'; +import { EventCategory, GoogleAnalyticsService } from '../services/google-analytics.service'; import { NavigationEnd, Router, RouterEvent } from '@angular/router'; +import { SetInstallPrompt, AppState, Online, Offline, ChangeInstallStatus } from './app.state'; +import { WINDOW } from '../services/window.token'; @Injectable({ providedIn: 'root', }) export class EventBus { + private renderer: Renderer2; constructor( private actions$: Actions, private store: Store, private router: Router, private analytics: GoogleAnalyticsService, private pageTitle: PageTitleService, + private rendererFactory: RendererFactory2, + @Inject(WINDOW) private readonly window: Window, ) { + this.renderer = rendererFactory.createRenderer(this.window, null); + this.renderer.listen(this.window, 'online', () => { + this.store.dispatch(new Online()); + }); + this.renderer.listen(this.window, 'offline', () => { + this.store.dispatch(new Offline()); + }); + + const installed = this.store.selectSnapshot(AppState.installed); + if (!installed) { + this.renderer.listen(this.window, 'beforeinstallprompt', event => { + event.preventDefault(); // Prevent default for older Chrome versions to prevent double install prompt + this.analytics.emitEvent(EventCategory.Install, 'available'); + this.store.dispatch(new SetInstallPrompt(event)); + }); + this.renderer.listen(this.window, 'appinstalled', event => { + event.preventDefault(); // Prevent default for older Chrome versions to prevent double install prompt + ga('send', 'event', 'install', 'installed'); + this.analytics.emitEvent(EventCategory.Install, 'installed'); + this.store.dispatch(new ChangeInstallStatus(true)); + }); + + this.actions$.pipe(ofActionSuccessful(SetInstallPrompt)).subscribe(async (action: SetInstallPrompt) => { + try { + console.log('platforms', action.payload.platforms); + // TODO: prompt user with MatSnackBar and call below if clicked. + action.payload.prompt(); + const choiceResult = await action.payload.userChoice; + console.log('choiceResult', choiceResult); + this.analytics.emitEvent(EventCategory.Install, choiceResult); + } catch (installError) { + this.analytics.emitEvent(EventCategory.Install, 'errored'); + } + }); + } + this.actions$.pipe(ofActionSuccessful(Login)).subscribe(action => console.log('Login........Action Successful')); this.actions$.pipe(ofActionErrored(Login)).subscribe(action => console.log('Login........Action Errored')); - this.actions$ - .pipe(ofActionSuccessful(LoginSuccess)) - .subscribe((action: LoginSuccess) => { - this.analytics.setUsername(action.payload.preferred_username); - }); + this.actions$.pipe(ofActionSuccessful(LoginSuccess)).subscribe((action: LoginSuccess) => { + this.analytics.setUsername(action.payload.preferred_username); + }); this.actions$ .pipe(ofActionSuccessful(ConnectWebSocket)) @@ -75,6 +113,5 @@ export class EventBus { this.pageTitle.setTitle(data.breadcrumbs); this.analytics.setPage(data.url); }); - } } diff --git a/libs/core/src/lib/state/preference.state.ts b/libs/core/src/lib/state/preference.state.ts index d7702b232..4bf4bb717 100644 --- a/libs/core/src/lib/state/preference.state.ts +++ b/libs/core/src/lib/state/preference.state.ts @@ -1,17 +1,119 @@ -import { State, Store } from '@ngxs/store'; +import { Action, State, StateContext, Store } from '@ngxs/store'; + +export type Language = 'en' | 'es' | 'de' | 'fr' | 'cn'; + +export enum ThemeName { + DEEPPURPLE_AMBER = 'DEEPPURPLE_AMBER', + INDIGO_PINK = 'INDIGO_PINK', + PINK_BLUEGREY = 'PINK_BLUEGREY', + PURPLE_GREEN = 'PURPLE_GREEN', +} + +export interface Theme { + readonly primary: string; + readonly accent: string; + readonly href: string; + readonly isDark: boolean; + readonly isDefault?: boolean; +} + +export const themes: Readonly> = new Map([ + [ + ThemeName.DEEPPURPLE_AMBER, + { + primary: '#673AB7', + accent: '#FFC107', + href: 'deeppurple-amber.css', + isDark: false, + }, + ], + [ + ThemeName.INDIGO_PINK, + { + primary: '#3F51B5', + accent: '#E91E63', + href: 'indigo-pink.css', + isDark: false, + isDefault: true, + }, + ], + [ + ThemeName.PINK_BLUEGREY, + { + primary: '#E91E63', + accent: '#607D8B', + href: 'pink-bluegrey.css', + isDark: true, + }, + ], + [ + ThemeName.PURPLE_GREEN, + { + primary: '#9C27B0', + accent: '#4CAF50', + href: 'purple-green.css', + isDark: true, + }, + ], +]); + +export class ChangeLanguage { + static readonly type = '[Preference] Change Language'; + constructor(public payload: Language) {} +} +export class ChangeTheme { + static readonly type = '[Preference] Change Theme'; + constructor(public payload: ThemeName) {} +} +export class EnableNotifications { + static readonly type = '[Preference] Enable Notifications'; +} +export class DisableNotifications { + static readonly type = '[Preference] Disable Notifications'; +} export interface PreferenceStateModel { - language?: string; - theme?: string; + language: Language; + theme: ThemeName; + enableNotifications: boolean; } @State({ name: 'preference', defaults: { language: 'en', - theme: 'deeppurple-amber', + theme: ThemeName.PINK_BLUEGREY, + enableNotifications: false, }, }) export class PreferenceState { constructor(private store: Store) {} + + @Action(ChangeLanguage) + changeLanguage({ patchState }: StateContext, { payload }: ChangeLanguage) { + patchState({ + language: payload, + }); + } + + @Action(ChangeTheme) + changeTheme({ patchState }: StateContext, { payload }: ChangeTheme) { + patchState({ + theme: payload, + }); + } + + @Action(EnableNotifications) + enableNotifications({ patchState }: StateContext) { + patchState({ + enableNotifications: true, + }); + } + + @Action(DisableNotifications) + disableNotifications({ patchState }: StateContext) { + patchState({ + enableNotifications: false, + }); + } } diff --git a/libs/dashboard/src/lib/containers/overview/overview.component.html b/libs/dashboard/src/lib/containers/overview/overview.component.html index 85e35854b..26a8d9806 100644 --- a/libs/dashboard/src/lib/containers/overview/overview.component.html +++ b/libs/dashboard/src/lib/containers/overview/overview.component.html @@ -1,19 +1,19 @@ - - - - - - - - - - - - - - - - - - + +
+ +
+ Grid List +
+ +
+ Layout +
+
+ Home +
+
diff --git a/libs/dashboard/src/lib/containers/profile/profile.component.html b/libs/dashboard/src/lib/containers/profile/profile.component.html new file mode 100644 index 000000000..42aee52a5 --- /dev/null +++ b/libs/dashboard/src/lib/containers/profile/profile.component.html @@ -0,0 +1,20 @@ + + +
+ + + Account Profile + + + + + + +
+ {{ entry.key }}:{{ entry.value | json }} +
+
+
+
+
+
diff --git a/libs/dashboard/src/lib/containers/profile/profile.component.scss b/libs/dashboard/src/lib/containers/profile/profile.component.scss new file mode 100644 index 000000000..77f2e0917 --- /dev/null +++ b/libs/dashboard/src/lib/containers/profile/profile.component.scss @@ -0,0 +1,5 @@ +:host { + display: block; + padding: 1.5%; + position: relative; +} diff --git a/libs/dashboard/src/lib/containers/profile/profile.component.spec.ts b/libs/dashboard/src/lib/containers/profile/profile.component.spec.ts new file mode 100644 index 000000000..692b2340e --- /dev/null +++ b/libs/dashboard/src/lib/containers/profile/profile.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProfileComponent } from './profile.component'; + +describe('ProfileComponent', () => { + let component: ProfileComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ProfileComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ProfileComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/libs/dashboard/src/lib/containers/profile/profile.component.ts b/libs/dashboard/src/lib/containers/profile/profile.component.ts new file mode 100644 index 000000000..dc7e3898b --- /dev/null +++ b/libs/dashboard/src/lib/containers/profile/profile.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { Crumb } from '@ngx-starter-kit/breadcrumbs'; +import { Store } from '@ngxs/store'; +import { AuthState } from '@ngx-starter-kit/auth'; + +@Component({ + selector: 'ngx-profile', + templateUrl: './profile.component.html', + styleUrls: ['./profile.component.scss'] +}) +export class ProfileComponent implements OnInit { + crumbs: ReadonlyArray = [{ name: 'Dashboard', link: '/dashboard' }, { name: 'Profile' }]; + profile: any; + constructor(private store: Store) { } + + ngOnInit() { + this.profile = this.store.selectSnapshot(AuthState.profile); + } + +} diff --git a/libs/dashboard/src/lib/containers/settings/settings.component.html b/libs/dashboard/src/lib/containers/settings/settings.component.html new file mode 100644 index 000000000..110f587ce --- /dev/null +++ b/libs/dashboard/src/lib/containers/settings/settings.component.html @@ -0,0 +1,51 @@ + + +
+ + + Preferences + + + +
+
+
Setting
+
Options
+
+
+
Language
+
+ + + {{ language }} + + +
+
+
+
Theme
+
+ + + {{ theme }} + + +
+
+
+
Notifications
+
+ + +
+
+
+
+ + +
+
diff --git a/libs/dashboard/src/lib/containers/settings/settings.component.scss b/libs/dashboard/src/lib/containers/settings/settings.component.scss new file mode 100644 index 000000000..0e75b1c5a --- /dev/null +++ b/libs/dashboard/src/lib/containers/settings/settings.component.scss @@ -0,0 +1,25 @@ +:host { + display: block; + padding: 1.5%; + position: relative; +} + + +.mat-table { + display: block; +} + +.mat-row, +.mat-header-row { + display: flex; + border-bottom-width: 1px; + border-bottom-style: solid; + align-items: center; + min-height: 60px; + padding: 0 24px; +} + +.add-padding-right { + padding-right: 8px; +} + diff --git a/libs/dashboard/src/lib/containers/settings/settings.component.spec.ts b/libs/dashboard/src/lib/containers/settings/settings.component.spec.ts new file mode 100644 index 000000000..91588f354 --- /dev/null +++ b/libs/dashboard/src/lib/containers/settings/settings.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SettingsComponent } from './settings.component'; + +describe('SettingsComponent', () => { + let component: SettingsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SettingsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/libs/dashboard/src/lib/containers/settings/settings.component.ts b/libs/dashboard/src/lib/containers/settings/settings.component.ts new file mode 100644 index 000000000..d415d177f --- /dev/null +++ b/libs/dashboard/src/lib/containers/settings/settings.component.ts @@ -0,0 +1,67 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Crumb } from '@ngx-starter-kit/breadcrumbs'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngxs/store'; +import { + ChangeLanguage, + ChangeTheme, + DisableNotifications, + EnableNotifications, Language, + PreferenceState, + PreferenceStateModel, PushNotificationService, ThemeName, +} from '@ngx-starter-kit/core'; +import { untilDestroy } from '@ngx-starter-kit/ngx-utils'; + +@Component({ + selector: 'ngx-settings', + templateUrl: './settings.component.html', + styleUrls: ['./settings.component.scss'], +}) +export class SettingsComponent implements OnInit, OnDestroy { + crumbs: ReadonlyArray = [{ name: 'Dashboard', link: '/dashboard' }, { name: 'Settings' }]; + preferences: PreferenceStateModel; + settingsForm: FormGroup; + languages: Language[] = ['en', 'es', 'de', 'fr', 'cn']; + themes: ThemeName[] = [ThemeName.DEEPPURPLE_AMBER, ThemeName.INDIGO_PINK, ThemeName.PINK_BLUEGREY, ThemeName.PURPLE_GREEN]; + + constructor(private fb: FormBuilder, private store: Store, private pnServ: PushNotificationService) {} + + ngOnInit() { + this.preferences = this.store.selectSnapshot(PreferenceState); + this.buildForm(); + + this.settingsForm + .get('selectedLanguage') + .valueChanges.pipe(untilDestroy(this)) + .subscribe(selectedLanguage => this.store.dispatch(new ChangeLanguage(selectedLanguage))); + + this.settingsForm + .get('selectedTheme') + .valueChanges.pipe(untilDestroy(this)) + .subscribe(selectedTheme => this.store.dispatch(new ChangeTheme(selectedTheme))); + + this.settingsForm + .get('enableNotifications') + .valueChanges.pipe(untilDestroy(this)) + .subscribe(enableNotifications => { + console.log('pnServ.available', this.pnServ.available); + if (enableNotifications) { + this.pnServ.register(); + this.store.dispatch(new EnableNotifications()); + } else { + this.pnServ.unregister(); + this.store.dispatch(new DisableNotifications()); + } + }); + } + + ngOnDestroy() {} + + buildForm() { + this.settingsForm = this.fb.group({ + selectedLanguage: [this.preferences.language], + selectedTheme: [this.preferences.theme], + enableNotifications: this.preferences.enableNotifications, + }); + } +} diff --git a/libs/dashboard/src/lib/dashboard.module.ts b/libs/dashboard/src/lib/dashboard.module.ts index dbe02d352..9a01bfbd5 100644 --- a/libs/dashboard/src/lib/dashboard.module.ts +++ b/libs/dashboard/src/lib/dashboard.module.ts @@ -7,11 +7,13 @@ import { ChatBoxModule } from '@ngx-starter-kit/chat-box'; import { DashboardLayoutComponent } from './containers/dashboard-layout/dashboard-layout.component'; import { OverviewComponent } from './containers/overview/overview.component'; +import { SettingsComponent } from './containers/settings/settings.component'; import { RainbowComponent } from './components/rainbow/rainbow.component'; import { QuickpanelModule } from '@ngx-starter-kit/quickpanel'; import { ToolbarModule } from '@ngx-starter-kit/toolbar'; import { SidenavModule } from '@ngx-starter-kit/sidenav'; import { environment } from '@env/environment'; +import { ProfileComponent } from './containers/profile/profile.component'; @NgModule({ imports: [ @@ -34,7 +36,17 @@ import { environment } from '@env/environment'; { path: 'overview', component: OverviewComponent, - data: { animation: 'overview' }, + data: {title: 'Overview' }, + }, + { + path: 'profile', + component: ProfileComponent, + data: { title: 'Settings', depth: '2' }, + }, + { + path: 'settings', + component: SettingsComponent, + data: { title: 'Settings', depth: '2' }, }, { path: '', @@ -55,6 +67,6 @@ import { environment } from '@env/environment'; }, ]), ], - declarations: [DashboardLayoutComponent, OverviewComponent, RainbowComponent], + declarations: [DashboardLayoutComponent, OverviewComponent, RainbowComponent, ProfileComponent, SettingsComponent], }) export class DashboardModule {} diff --git a/libs/notifications/src/lib/notifications.service.ts b/libs/notifications/src/lib/notifications.service.ts index 83cee0fb6..ee32b5c55 100644 --- a/libs/notifications/src/lib/notifications.service.ts +++ b/libs/notifications/src/lib/notifications.service.ts @@ -29,6 +29,19 @@ export class NotificationsService extends EntityService { ); } + /** + * Util for showing native Notifications + * @param noti: { + * title: 'NGX WebApp Notification', + * options: { + * body: payload.message, + * icon: 'assets/icons/icon-72x72.png', + * data: { + * click_url: '/index.html', + * }, + * }, + * } + */ async showNativeNotification(noti: { title: string; options?: Partial }) { if ( this.featureService.detectFeature(BrowserFeatureKey.NotificationsAPI).supported && diff --git a/libs/notifications/src/lib/notifications.state.ts b/libs/notifications/src/lib/notifications.state.ts index 8b64fa06b..0f99fc48c 100644 --- a/libs/notifications/src/lib/notifications.state.ts +++ b/libs/notifications/src/lib/notifications.state.ts @@ -35,7 +35,13 @@ export class NotificationsState implements NgxsOnInit { add({ getState, setState, patchState }: StateContext, { payload }: AddNotification) { setState([...getState(), payload]); if (payload.native) { - return this.notificationsService.showNativeNotification({title: payload.message, options: { body: payload.message }}); + return this.notificationsService.showNativeNotification({ + title: 'NGX WebApp Notification', + options: { + body: payload.message, + icon: 'assets/icons/icon-72x72.png', + }, + }); } } diff --git a/libs/toolbar/src/lib/components/user-menu/user-menu.component.html b/libs/toolbar/src/lib/components/user-menu/user-menu.component.html index 8f6e555ec..973fad251 100644 --- a/libs/toolbar/src/lib/components/user-menu/user-menu.component.html +++ b/libs/toolbar/src/lib/components/user-menu/user-menu.component.html @@ -11,19 +11,19 @@
- Profile + Profile - Settings + Settings - Help + Help diff --git a/package-lock.json b/package-lock.json index dace68767..6d1c406ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1687,6 +1687,14 @@ "tslib": "^1.9.0" } }, + "@ngxs/storage-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@ngxs/storage-plugin/-/storage-plugin-3.2.0.tgz", + "integrity": "sha1-KXMKBohJKQAYuu7Qe0S/O0KEol8=", + "requires": { + "tslib": "^1.9.0" + } + }, "@ngxs/store": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@ngxs/store/-/store-3.2.0.tgz", diff --git a/package.json b/package.json index 152692b60..aa5564fcb 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "@ngxs/devtools-plugin": "^3.2.0", "@ngxs/form-plugin": "^3.2.0", "@ngxs/router-plugin": "^3.2.0", + "@ngxs/storage-plugin": "^3.2.0", "@ngxs/store": "^3.2.0", "@trademe/ng-defer-load": "^2.0.0", "@xmlking/api-ai-javascript": "^2.0.0-beta.22",