Skip to content

Commit d58bc4e

Browse files
committed
Fix header safe-area inset, avatar flicker on resume, and add refresh
- Header: let the toolbar grow by env(safe-area-inset-top) instead of letting the padding eat into Material's fixed toolbar height, which pushed the content under the iOS status bar in standalone PWA mode. - Avatar: derive the profile with linkedSignal so the last known value is retained while user data is momentarily empty during a token renewal on resume, instead of blanking out the avatar. - Renewal: swallow forced-refresh errors so a failed renew on resume can't surface as an unhandled error. - Refresh: add an in-app refresh button on the home page, since the browser disables pull-to-refresh in standalone PWA mode.
1 parent 9ae6e47 commit d58bc4e

6 files changed

Lines changed: 64 additions & 21 deletions

File tree

client/src/app/app.component.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
.header {
22
border-bottom: 1px solid var(--bt-outline);
33
display: block;
4+
/* Let the toolbar grow by the status-bar inset instead of letting the
5+
padding eat into Material's fixed toolbar height (which crushes the
6+
content under the iOS status bar in standalone PWA mode). */
7+
height: auto;
48
padding: env(safe-area-inset-top) 0 0;
59

610
nav {
@@ -10,7 +14,7 @@
1014
display: flex;
1115
justify-content: space-between;
1216
align-items: center;
13-
height: 100%;
17+
min-height: var(--mat-toolbar-standard-height, 64px);
1418

1519
a[mat-button] {
1620
font-size: 16px;

client/src/app/home/home.component.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
padding: 0 2rem;
66
}
77

8+
.page-toolbar {
9+
display: flex;
10+
justify-content: flex-end;
11+
margin-bottom: 0.5rem;
12+
}
13+
814
h1 {
915
color: var(--bt-text-strong);
1016
margin-bottom: 0.5rem;

client/src/app/home/home.component.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
<div class="page-toolbar">
2+
<button
3+
mat-icon-button
4+
aria-label="Refresh greeting"
5+
(click)="greeting.reload()"
6+
[disabled]="greeting.isLoading()"
7+
>
8+
<mat-icon>refresh</mat-icon>
9+
</button>
10+
</div>
11+
112
@if (greeting.isLoading()) {
213
<bt-bar-loader class="page-loading" label="Loading greeting" />
314
} @else {

client/src/app/home/home.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Component, inject } from '@angular/core';
2+
import { MatButtonModule } from '@angular/material/button';
23
import { MatCardModule } from '@angular/material/card';
4+
import { MatIconModule } from '@angular/material/icon';
35
import { BarLoaderComponent } from '@mucsi96/angular-material-theme';
46
import { GreetingService } from '../greeting.service';
57

@@ -8,7 +10,9 @@ import { GreetingService } from '../greeting.service';
810
standalone: true,
911
imports: [
1012
BarLoaderComponent,
13+
MatButtonModule,
1114
MatCardModule,
15+
MatIconModule,
1216
],
1317
templateUrl: './home.component.html',
1418
styleUrl: './home.component.css',
Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,48 @@
1-
import { computed, Injectable, inject } from '@angular/core';
1+
import { Injectable, inject, linkedSignal } from '@angular/core';
22
import { AuthService } from './auth.service';
33

4+
interface UserProfile {
5+
name: string;
6+
initials: string;
7+
}
8+
49
@Injectable({
510
providedIn: 'root',
611
})
712
export class UserProfileService {
813
private readonly authService = inject(AuthService);
914

10-
profile = computed(() => {
11-
const userDataResult = this.authService.userData();
12-
const userData = userDataResult?.userData;
13-
const name = userData?.name ?? userData?.preferred_username;
15+
// Derive the profile from the auth user data, but keep the last known value
16+
// while the user data is momentarily empty during a token renewal (which
17+
// happens when the installed PWA resumes on mobile). A plain computed would
18+
// blank out and make the avatar disappear; linkedSignal lets us fall back to
19+
// the previous value instead.
20+
profile = linkedSignal<unknown, UserProfile | undefined>({
21+
source: this.authService.userData,
22+
computation: (userDataResult, previous) => {
23+
const userData = (
24+
userDataResult as
25+
| { userData?: { name?: string; preferred_username?: string } }
26+
| undefined
27+
)?.userData;
28+
const name = userData?.name ?? userData?.preferred_username;
1429

15-
if (!name) {
16-
return undefined;
17-
}
30+
if (!name) {
31+
return previous?.value;
32+
}
1833

19-
return {
20-
name,
21-
initials: this.getInitials(name),
22-
};
34+
return {
35+
name,
36+
initials: this.getInitials(name),
37+
};
38+
},
2339
});
2440

25-
private getInitials(name: string | undefined): string {
26-
if (!name) return '';
27-
const initials = name
41+
private getInitials(name: string): string {
42+
return name
2843
.split(' ')
29-
.map((n) => n[0])
30-
.join('');
31-
return initials.toUpperCase();
44+
.map((part) => part[0])
45+
.join('')
46+
.toUpperCase();
3247
}
3348
}

client/src/app/utils/token-renewal.service.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DestroyRef, inject, Injectable } from '@angular/core';
22
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
33
import { OidcSecurityService } from 'angular-auth-oidc-client';
4-
import { fromEvent, merge, throttleTime } from 'rxjs';
4+
import { catchError, EMPTY, fromEvent, merge, throttleTime } from 'rxjs';
55

66
/**
77
* Keeps the access token fresh on mobile.
@@ -45,7 +45,10 @@ export class TokenRenewalService {
4545
if (isAuthenticated) {
4646
this.oidcSecurityService
4747
.forceRefreshSession()
48-
.pipe(takeUntilDestroyed(this.destroyRef))
48+
.pipe(
49+
catchError(() => EMPTY),
50+
takeUntilDestroyed(this.destroyRef)
51+
)
4952
.subscribe();
5053
}
5154
});

0 commit comments

Comments
 (0)