Skip to content

Commit

Permalink
feat: various fixes and improvements (#96)
Browse files Browse the repository at this point in the history
- fix: theme not switching with system theme when not on settings page
- feat: add disconnected hint
- fix: ensure token is not expired when calling APIs (#95)
- feat: add explanation why event cannot be started (#87)

Closes: #87, #95
  • Loading branch information
MaSch0212 authored Jun 23, 2024
1 parent 9ae184c commit ec59020
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 122 deletions.
7 changes: 5 additions & 2 deletions src/client/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { routes } from './app.routes';
import { environment } from './environments/environment';
import { AuthInterceptor } from './services/auth.interceptor';
import { AuthService } from './services/auth.service';
import { ThemeService } from './services/theme.service';
import { WebPushService } from './services/web-push.service';

export const appConfig: ApplicationConfig = {
Expand Down Expand Up @@ -58,11 +59,13 @@ export const appConfig: ApplicationConfig = {
provide: APP_INITIALIZER,
multi: true,
useFactory: () => {
// Inject services that need to be initialized
inject(WebPushService);
inject(ThemeService);

const authService = inject(AuthService);
const webPushService = inject(WebPushService);
return async () => {
await authService.init();
webPushService.init();
};
},
},
Expand Down
105 changes: 52 additions & 53 deletions src/client/src/app/components/app/menu/menu.component.html
Original file line number Diff line number Diff line change
@@ -1,24 +1,65 @@
@if (isAdmin()) {
<p-menubar class="relative block" [model]="menuItems()" [autoDisplay]="false">
<p-menubar class="relative block" [model]="adminMenuItems()" [autoDisplay]="false">
<ng-template pTemplate="end">
<div class="flex flex-row flex-wrap items-baseline justify-end gap-x-4">
@if (title()) {
<span class="font-semibold">{{ title() }}</span>
<span class="ml-8 text-xs">{{ translations.title() }}</span>
} @else {
<span class="font-semibold">{{ translations.title() }}</span>
<div class="flex flex-row gap-4">
<div class="flex flex-row flex-wrap items-baseline justify-end gap-x-4 text-end">
@if (title()) {
<span class="font-semibold">{{ title() }}</span>
<span class="ml-8 text-xs">{{ translations.title() }}</span>
} @else {
<span class="font-semibold">{{ translations.title() }}</span>
}
</div>
@if (!isServerConnected()) {
<p-button
[icon]="'i-[mdi--wifi-off]'"
[rounded]="true"
[text]="true"
[severity]="'danger'"
class="-my-8 self-center"
styleClass="text-[1.5rem]"
(onClick)="offlinePanel.toggle($event)"
/>
<p-overlayPanel #offlinePanel [dismissable]="true">
<span>{{ translations.nav_offline() }}</span>
</p-overlayPanel>
}
</div>
<ng-container *ngTemplateOutlet="updateButtonTemplate"></ng-container>
</ng-template>
</p-menubar>
} @else {
<div class="relative flex flex-row gap-2">
@for (item of menuItems(); track index; let index = $index) {
<ng-container
*ngTemplateOutlet="menuItemTemplate; context: { $implicit: item, index }"
></ng-container>
<p-button
[icon]="'i-[mdi--home]'"
[pTooltip]="translations.nav_home()"
[rounded]="true"
[text]="true"
styleClass="h-14 w-14 p-4 text-[1.5rem]"
[routerLink]="'/home'"
/>
<div class="grow"></div>
@if (!isServerConnected()) {
<p-button
[icon]="'i-[mdi--wifi-off]'"
[rounded]="true"
[text]="true"
[severity]="'danger'"
styleClass="h-14 w-14 p-4 text-[1.5rem]"
(onClick)="offlinePanel.toggle($event)"
/>
<p-overlayPanel #offlinePanel [dismissable]="true">
<span>{{ translations.nav_offline() }}</span>
</p-overlayPanel>
}
<p-button
[icon]="'i-[mdi--cog]'"
[pTooltip]="translations.nav_settings()"
[rounded]="true"
[text]="true"
styleClass="h-14 w-14 p-4 text-[1.5rem]"
[routerLink]="'/user-settings'"
/>
<ng-container *ngTemplateOutlet="updateButtonTemplate"></ng-container>
</div>
}
Expand All @@ -39,45 +80,3 @@
</div>
}
</ng-template>

<ng-template #menuItemTemplate let-item let-index="index">
@if (item.visible !== false) {
@if (item.separator) {
@if (item.state?.['grow']) {
<div class="grow"></div>
} @else {
<div class="mx-4 my-auto h-8 w-[1px] bg-surface-d"></div>
}
} @else if (item.items) {
@if (item.state?.['expand'] === true) {
@for (subItem of item.items; track index; let index = $index) {
<ng-container
*ngTemplateOutlet="menuItemTemplate; context: { $implicit: subItem, index }"
></ng-container>
}
} @else {
<div>
<p-button
[icon]="item.icon"
[pTooltip]="item.label"
[rounded]="true"
[text]="true"
styleClass="h-14 w-14 p-4 text-[1.5rem]"
(onClick)="menu.toggle($event)"
/>
<p-menu #menu [model]="item.items" [popup]="true" />
</div>
}
} @else {
<p-button
[icon]="item.icon"
[pTooltip]="item.label"
[rounded]="true"
[text]="true"
styleClass="h-14 w-14 p-4 text-[1.5rem]"
[routerLink]="item.routerLink"
(onClick)="item.command?.({ originalEvent: $event, item, index })"
/>
}
}
</ng-template>
21 changes: 11 additions & 10 deletions src/client/src/app/components/app/menu/menu.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { MenuItem } from 'primeng/api';
import { ButtonModule } from 'primeng/button';
import { MenuModule } from 'primeng/menu';
import { MenubarModule } from 'primeng/menubar';
import { OverlayPanelModule } from 'primeng/overlaypanel';
import { TooltipModule } from 'primeng/tooltip';
import { filter, fromEvent, map, merge } from 'rxjs';

Expand All @@ -20,7 +21,14 @@ import { chainSignals } from '../../../utils/signal.utils';
@Component({
selector: 'app-menu',
standalone: true,
imports: [ButtonModule, CommonModule, MenubarModule, MenuModule, TooltipModule],
imports: [
ButtonModule,
CommonModule,
MenubarModule,
MenuModule,
TooltipModule,
OverlayPanelModule,
],
templateUrl: './menu.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
Expand Down Expand Up @@ -56,13 +64,13 @@ export class MenuComponent {
),
{ initialValue: false }
);
protected readonly isServerConnected = this._realtimeEventsService.isConnected;

protected readonly menuItems = computed<MenuItem[]>(() => [
protected readonly adminMenuItems = computed<MenuItem[]>(() => [
{
label: this.translations.nav_home(),
icon: 'i-[mdi--home]',
routerLink: '/home',
visible: this.isLoggedIn(),
},
{
label: this.translations.nav_manage(),
Expand All @@ -84,18 +92,11 @@ export class MenuComponent {
routerLink: '/manage/events',
},
],
visible: this.isAdmin(),
},
{
separator: true,
visible: !this.isAdmin(),
state: { grow: true },
},
{
label: this.translations.nav_settings(),
icon: 'i-[mdi--cog]',
routerLink: '/user-settings',
visible: this.isLoggedIn(),
},
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,25 @@
<span>{{ event.registrationDeadline | date: 'medium' : undefined : locale() }}</span>
</div>
@if (canStart()) {
<p-button
class="self-center"
icon="i-[mdi--play]"
[label]="translations.events_start()"
(onClick)="startEvent()"
[loading]="isStartBusy()"
[disabled]="!allowToStart()"
/>
<div class="flex flex-col gap-2">
<p-button
class="self-center"
icon="i-[mdi--play]"
[label]="translations.events_start()"
(onClick)="startEvent()"
[loading]="isStartBusy()"
[disabled]="!allowToStart()"
/>
@if (!hasInstances()) {
<small class="text-warning text-center">{{
translations.events_warning_notStartableNoInstances()
}}</small>
} @else if (!allTimeslotsHaveMaps()) {
<small class="text-warning text-center">{{
translations.events_warning_notStartableMissingMap()
}}</small>
}
</div>
}
@if (canCommit()) {
<p-button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,18 @@ export class EventDetailsComponent {
protected readonly hasInstances = computed(() =>
this.timeslots().some(x => x.instances.length > 0)
);
protected readonly allTimeslotsHaveMaps = computed(
() =>
!this.event()
?.timeslots.filter(x => x.instances.length > 0)
.some(x => x.mapId === null || x.mapId === undefined)
);

protected readonly canBuildInstances = computed(() =>
ifTruthy(this.event(), event => event.registrationDeadline.getTime() < this.now(), false)
);
protected readonly canStart = computed(
() =>
this.canBuildInstances() && this.event() && !this.event()?.startedAt && this.hasInstances()
() => this.canBuildInstances() && this.event() && !this.event()?.startedAt
);

protected readonly canCommit = computed(
Expand All @@ -103,10 +108,7 @@ export class EventDetailsComponent {
);

protected readonly allowToStart = computed(
() =>
!this.event()
?.timeslots.filter(x => x.instances.length > 0)
.some(x => x.mapId === null || x.mapId === undefined)
() => this.hasInstances() && this.allTimeslotsHaveMaps()
);

constructor() {
Expand Down
7 changes: 6 additions & 1 deletion src/client/src/app/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"eventTimeslot": "Zeitslot",
"maps": "Bahnen",
"users": "Benutzer",
"settings": "Einstellungen"
"settings": "Einstellungen",
"offline": "Die Verbindung zum Server wurde unterbrochen. Die Verbindung wird automatisch wiederhergestellt, sobald sie wieder verfügbar ist."
},
"settings": {
"general": {
Expand Down Expand Up @@ -130,6 +131,10 @@
"title": "Veranstaltung freigeben",
"text": "Möchtest du die Veranstlaung {{date}} wirklich freigeben? Die Zeitfenster können nicht mehr angepasst werden und die Spieler werden über ein neues Event benachrichtigt"
},
"warning": {
"notStartableNoInstances": "Die Veranstaltung kann nicht gestartet werden, da die Gruppen noch nicht gebildet wurden.",
"notStartableMissingMap": "Die Veranstaltung kann nicht gestartet werden, da nicht für alle gespielten Zeitslots eine Bahn festgelegt wurde."
},
"error": {
"load": "Fehler beim Laden der Veranstaltungen.",
"loadOne": "Fehler beim Laden der Veranstaltung.",
Expand Down
7 changes: 6 additions & 1 deletion src/client/src/app/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"eventTimeslot": "Timeslot",
"maps": "Maps",
"users": "Users",
"settings": "Settings"
"settings": "Settings",
"offline": "The connection to the server has been lost. The connection will be restored automatically once it is available again."
},
"settings": {
"general": {
Expand Down Expand Up @@ -130,6 +131,10 @@
"title": "Commit event",
"text": "Do you really want to release the event {{date}}? The time slots can no longer be adjusted and players will be notified of a new event"
},
"warning": {
"notStartableNoInstances": "The event cannot be started because the groups have not been built yet.",
"notStartableMissingMap": "The event cannot be started because not all played timeslots have a map assigned."
},
"error": {
"load": "Failed to load events.",
"loadOne": "Failed to load event.",
Expand Down
26 changes: 17 additions & 9 deletions src/client/src/app/services/auth.interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { from, Observable, switchMap } from 'rxjs';

import { AuthService } from './auth.service';

Expand All @@ -9,14 +9,22 @@ export class AuthInterceptor implements HttpInterceptor {
private readonly _authService = inject(AuthService);

public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = this._authService.token()?.token;
if (token && !req.headers.has('Authorization')) {
req = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
});
if (req.url.includes('api/auth/token')) {
return next.handle(req);
}
return next.handle(req);

return from(this._authService.ensureTokenNotExpired()).pipe(
switchMap(() => {
const token = this._authService.token()?.token;
if (token && !req.headers.has('Authorization')) {
req = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
});
}
return next.handle(req);
})
);
}
}
Loading

0 comments on commit ec59020

Please sign in to comment.