Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add send test notification #112

Merged
merged 12 commits into from
Jul 3, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ import { TranslateService } from '../../services/translate.service';
class="pointer-events-none absolute top-1/2 -translate-x-[calc(100%+8px)] -translate-y-1/2 truncate rounded bg-green-100 px-4 py-2 text-green-900 dark:bg-green-900 dark:text-green-200"
[showTrigger]="showTrigger()"
>
<span class="i-[mdi--check]"></span>
{{ translations.shared_saved() }}
<div class="flex flex-row items-center gap-1">
<span class="i-[mdi--check]"></span>
<ng-content>
{{ translations.shared_saved() }}
</ng-content>
</div>
</app-fading-message>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
Expand Down
2 changes: 1 addition & 1 deletion src/client/src/app/components/app/menu/menu.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<ng-template #updateButtonTemplate>
@if (newVersionAvailable()) {
<div class="top-5 absolute-center">
<div [@flyInTopAnimation]>
<div>
<p-button
[icon]="'i-[mdi--cloud-download]'"
[label]="translations.shared_update()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@ <h1 class="m-0">{{ translations.nav_settings() }}</h1>
</div>

@if (settings.enableNotifications && notificationsEnabled()) {
<div class="flex flex-row items-center gap-4 pt-2">
<div class="grow">
{{ translations.settings_notifications_testNotification_text() }}
</div>
<div class="relative -my-4 p-0">
<p-button
class="items-center"
[label]="translations.shared_send()"
icon="i-[mdi--send]"
size="small"
[severity]="'primary'"
(onClick)="sendTestNotification()"
/>
<app-saved-fading-message [showTrigger]="settingsSaveStates.testSend | async">{{
translations.shared_sent()
}}</app-saved-fading-message>
</div>
</div>
<h3 class="m-0">{{ translations.settings_notifications_notify_title() }}</h3>
<div class="flex flex-col gap-4 pl-4">
<div class="flex flex-row items-center gap-4">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
updateUserSettingsAction,
} from '../../+state/user-settings';
import { keepUserSettingsLoaded } from '../../+state/user-settings/user-settings.utils';
import { NotificationsService } from '../../api/services';
import { ResetNgModelDirective } from '../../directives/reset-ng-model.directive';
import { UserSettings } from '../../models/parsed-models';
import { AuthService } from '../../services/auth.service';
Expand Down Expand Up @@ -75,6 +76,7 @@ export class UserSettingsComponent {
private readonly _actions$ = inject(Actions);
private readonly _messageService = inject(MessageService);
private readonly _webPushService = inject(WebPushService);
private readonly _notificationService = inject(NotificationsService);

private readonly _loadActionState = selectSignal(selectUserSettingsActionState('load'));
private readonly _updateActionState = selectSignal(selectUserSettingsActionState('update'));
Expand Down Expand Up @@ -132,6 +134,7 @@ export class UserSettingsComponent {
);

protected readonly isUpdatingPushSubscription = signal(false);
protected readonly sendPush = signal(false);
protected readonly isLoading = computed(() => isActionBusy(this._loadActionState()));
protected readonly isUpdating = computed(() => isActionBusy(this._updateActionState()));
protected readonly hasFailed = computed(() => hasActionFailed(this._loadActionState()));
Expand All @@ -142,6 +145,7 @@ export class UserSettingsComponent {
notifyEventStart: this.getSaveState('notifyOnEventStart'),
notifyEventUpdate: this.getSaveState('notifyOnEventUpdated'),
notifyTimeslotStart: this.getSaveState('notifyOnTimeslotStart'),
testSend: toObservable(this.sendPush),
};

constructor() {
Expand All @@ -166,6 +170,19 @@ export class UserSettingsComponent {
this._store.dispatch(updateUserSettingsAction(changes));
}

protected async sendTestNotification() {
this.sendPush.set(false);
const response = await this._notificationService.sendNotification({
body: {
title: this.translations.users_notificationDialog_titleDefault(),
body: this.translations.users_notificationDialog_bodyDefault(),
},
});
if (response.ok) {
this.sendPush.set(true);
}
}

protected async toggleNotifications(enabled: boolean) {
if (!this.notificationsPossible) return;
this.isUpdatingPushSubscription.set(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<p-dialog
[header]="translations.users_notificationDialog_dialogTitle()"
[visible]="visible()"
(visibleChange)="visible.set($event)"
[modal]="true"
[draggable]="false"
[resizable]="false"
styleClass="min-w-[80vw]"
>
<div class="flex flex-col gap-4" [formGroup]="form">
<div>
{{ translations.users_notificationDialog_sendTo() | interpolate: user() }}
</div>

<span class="p-float-label">
<input
pInputText
[id]="id('title')"
type="text"
formControlName="title"
autocomplete="off"
pAutoFocus
[autofocus]="true"
[placeholder]="translations.users_notificationDialog_titleDefault()"
/>
<label [htmlFor]="id('title')">{{ translations.users_notificationDialog_title() }}</label>
</span>

<span class="p-float-label">
<textarea
pInputTextarea
[id]="id('body')"
formControlName="body"
autocomplete="off"
[placeholder]="translations.users_notificationDialog_bodyDefault()"
></textarea>
<label [htmlFor]="id('body')">{{ translations.users_notificationDialog_body() }}</label>
</span>
</div>
<ng-template pTemplate="footer">
<p-button
[disabled]="isLoading()"
[text]="true"
label="{{ translations.shared_cancel() }}"
(onClick)="close()"
/>
<p-button
[disabled]="isLoading()"
label="{{ translations.shared_send() }}"
(onClick)="submit()"
/>
</ng-template>
</p-dialog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { AutoFocusModule } from 'primeng/autofocus';
import { ButtonModule } from 'primeng/button';
import { DialogModule } from 'primeng/dialog';
import { InputTextModule } from 'primeng/inputtext';
import { InputTextareaModule } from 'primeng/inputtextarea';

import { NotificationsService } from '../../../api/services';
import { InterpolatePipe } from '../../../directives/interpolate.pipe';
import { User } from '../../../models/parsed-models';
import { TranslateService } from '../../../services/translate.service';

@Component({
selector: 'app-user-push-dialog',
standalone: true,
imports: [
AutoFocusModule,
ButtonModule,
CommonModule,
DialogModule,
InputTextModule,
InputTextareaModule,
InterpolatePipe,
ReactiveFormsModule,
],
templateUrl: './user-push-dialog.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserPushDialogComponent {
protected readonly translations = inject(TranslateService).translations;
private readonly _randomId = Math.random().toString(36).substring(2, 9);

private readonly _formBuilder = inject(FormBuilder);
private readonly _notificationsService = inject(NotificationsService);

protected readonly visible = signal(false);
protected readonly isLoading = signal(false);
protected readonly user = signal<User | undefined>(undefined);
protected readonly form = this._formBuilder.group({
title: this._formBuilder.control(''),
body: this._formBuilder.control(''),
});

public open(user: User): void {
this.user.set(user);
this.visible.set(true);
}

public close(): void {
this.visible.set(false);
}

protected async submit() {
if (!this.form.valid) {
this.form.markAllAsTouched();
return;
}

const user = this.user();
if (!user) return;

this.isLoading.set(true);
try {
await this._notificationsService.sendNotification({
body: {
userId: user.id,
title: this.form.value.title || this.translations.users_notificationDialog_titleDefault(),
body: this.form.value.body || this.translations.users_notificationDialog_bodyDefault(),
},
});
this.close();
} finally {
this.isLoading.set(false);
}
}

protected id(purpose: string) {
return `${purpose}-${this._randomId}`;
}
}
10 changes: 10 additions & 0 deletions src/client/src/app/components/users/users.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@
[class.border-t-[1px]]="index > 0"
>
<app-user-item [user]="user" class="min-w-0 grow" />
@if (user.hasPushSubscription) {
<p-button
icon="i-[mdi--bell]"
[rounded]="true"
[text]="true"
size="small"
(onClick)="pushDialog.open(user)"
/>
}
<p-button
icon="i-[mdi--pencil-outline]"
[rounded]="true"
Expand Down Expand Up @@ -76,3 +85,4 @@
}

<app-user-dialog #dialog />
<app-user-push-dialog #pushDialog />
2 changes: 2 additions & 0 deletions src/client/src/app/components/users/users.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ProgressSpinnerModule } from 'primeng/progressspinner';

import { UserDialogComponent } from './user-dialog/user-dialog.component';
import { UserItemComponent } from './user-item/user-item.component';
import { UserPushDialogComponent } from './user-push-dialog/user-push-dialog.component';
import { isActionBusy, hasActionFailed } from '../../+state/action-state';
import {
keepUsersLoaded,
Expand Down Expand Up @@ -37,6 +38,7 @@ function userMatchesFilter(map: User | undefined, lowerCaseFilter: string): map
InputTextModule,
MessagesModule,
UserDialogComponent,
UserPushDialogComponent,
UserItemComponent,
ProgressSpinnerModule,
],
Expand Down
15 changes: 14 additions & 1 deletion src/client/src/app/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
"title": "Benachrichtigungen",
"enabledOnAllDevices": "Auf allen Geräten aktiviert",
"eanbled": "Auf diesem Gerät aktiviert",
"testNotification": {
"text": "Testbenachrichtigung"
},
"errors": {
"notSupported": "Benachrichtigungen werden von diesem Browser nicht unterstützt. Gegenebenfalls muss die Webseite auf dem Home-Bildschirm installiert werden.",
"notGranted": {
Expand Down Expand Up @@ -194,6 +197,14 @@
},
"error": {
"load": "Fehler beim Laden der Benutzer."
},
"notificationDialog": {
"dialogTitle": "Spieler Benachrichtigen",
"sendTo": "senden an \"{{alias}}\"",
"title": "Titel",
"titleDefault": "Testbenachrichtigung",
"body": "Inhalt",
"bodyDefault": "Benachrichtigungen funktionieren!!! Wohoo ⛳"
}
},
"playerEvents": {
Expand Down Expand Up @@ -247,7 +258,9 @@
"commit": "Freigeben",
"minute": "Minute",
"minutes": "Minuten",
"none": "Keine"
"none": "Keine",
"send": "Senden",
"sent": "Gesendet"
},
"validation": {
"required": "Darf nicht leer sein.",
Expand Down
15 changes: 14 additions & 1 deletion src/client/src/app/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
"title": "Notifications",
"enabledOnAllDevices": "Enabled on all devices",
"eanbled": "Enabled on this device",
"testNotification": {
"text": "Test notification"
},
"errors": {
"notSupported": "Notifications are not supported by this browser. Probably this website needs to be installed onto the homescreen.",
"notGranted": {
Expand Down Expand Up @@ -194,6 +197,14 @@
},
"error": {
"load": "Failed to load users."
},
"notificationDialog": {
"dialogTitle": "Notify player",
"sendTo": "senden an \"{{alias}}\"",
"title": "title",
"titleDefault": "test notification",
"body": "body",
"bodyDefault": "Notifications are working!!! Wohoo ⛳"
}
},
"playerEvents": {
Expand Down Expand Up @@ -247,7 +258,9 @@
"commit": "Commit",
"minute": "Minute",
"minutes": "Minutes",
"none": "None"
"none": "None",
"send": "send",
"sent": "sent"
},
"validation": {
"required": "Cannot be blank.",
Expand Down
Loading
Loading