>({})
@@ -128,9 +130,12 @@ export class FeedsPage implements OnInit {
.refreshAllFeeds()
.pipe(
takeUntilDestroyed(this.destroyRef),
- catchError((e) => {
+ catchError((error) => {
this.isRefreshingAll.set(false)
- console.error(e)
+ console.error(error)
+ this.notificationsService.setNotification({
+ message: error.error.message,
+ })
return of(null)
}),
)
diff --git a/src/app/pages/status-page/status-page.html b/src/app/pages/status-page/status-page.html
index d7ddc81..4ec7cdc 100644
--- a/src/app/pages/status-page/status-page.html
+++ b/src/app/pages/status-page/status-page.html
@@ -1 +1,16 @@
+Web client
+@if (updateFound$ | async) {
+
+ A new version of the web client is available.
+
+
+ - Current version hash: {{ currentVersion$ | async }}
+ - Next version hash: {{ nextVersion$ | async }}
+
+
+} @else {
+ You are using the latest version.
+ Current version hash: {{ currentVersion$ | async }}
+}
+Backend
diff --git a/src/app/pages/status-page/status-page.ts b/src/app/pages/status-page/status-page.ts
index 851f3fe..2f81e99 100644
--- a/src/app/pages/status-page/status-page.ts
+++ b/src/app/pages/status-page/status-page.ts
@@ -1,15 +1,23 @@
import { Component, inject, OnInit } from '@angular/core'
import { HealthStatus } from '../../components/health-status/health-status'
import { TitleService } from '../../services/title-service'
+import { UpdateButton } from '../../components/update-button/update-button'
+import { AppUpdate } from '../../services/app-update'
+import { AsyncPipe } from '@angular/common'
@Component({
selector: 'app-status-page',
- imports: [HealthStatus],
+ imports: [HealthStatus, UpdateButton, AsyncPipe],
templateUrl: './status-page.html',
styleUrl: './status-page.css',
})
export class StatusPage implements OnInit {
private readonly titleService = inject(TitleService)
+ private readonly appUpdates = inject(AppUpdate)
+
+ updateFound$ = this.appUpdates.updateFound
+ currentVersion$ = this.appUpdates.currentVersion
+ nextVersion$ = this.appUpdates.nextVersion
ngOnInit() {
this.titleService.setTitle('User')
diff --git a/src/app/pages/welcome-page/welcome-page.css b/src/app/pages/welcome-page/welcome-page.css
index a9be8ae..c9656c9 100644
--- a/src/app/pages/welcome-page/welcome-page.css
+++ b/src/app/pages/welcome-page/welcome-page.css
@@ -9,7 +9,13 @@
.details {
display: flex;
flex-direction: column;
- padding-inline: 1rem;
+ padding: 0.5rem 1rem 1.5rem;
margin: 0;
gap: 1rem;
}
+
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
diff --git a/src/app/pages/welcome-page/welcome-page.html b/src/app/pages/welcome-page/welcome-page.html
index 232ed1a..4c8de96 100644
--- a/src/app/pages/welcome-page/welcome-page.html
+++ b/src/app/pages/welcome-page/welcome-page.html
@@ -1,10 +1,11 @@
-
+
link
GitHub
+
+ link
+ GitLab
+
@@ -55,6 +64,14 @@
diff --git a/src/app/pages/welcome-page/welcome-page.ts b/src/app/pages/welcome-page/welcome-page.ts
index 5abe6ad..9e0038a 100644
--- a/src/app/pages/welcome-page/welcome-page.ts
+++ b/src/app/pages/welcome-page/welcome-page.ts
@@ -1,4 +1,4 @@
-import { Component } from '@angular/core'
+import { Component, inject, OnInit } from '@angular/core'
import { MatButton, MatFabButton } from '@angular/material/button'
import { RouterLink } from '@angular/router'
import {
@@ -9,6 +9,8 @@ import {
} from '@angular/material/expansion'
import { MatCardModule } from '@angular/material/card'
import { MatIconModule } from '@angular/material/icon'
+import { HealthService } from '../../services/health-service'
+import { UpdateButton } from '../../components/update-button/update-button'
@Component({
selector: 'app-welcome-page',
@@ -22,8 +24,15 @@ import { MatIconModule } from '@angular/material/icon'
MatIconModule,
MatFabButton,
MatAccordion,
+ UpdateButton,
],
templateUrl: './welcome-page.html',
styleUrl: './welcome-page.css',
})
-export class WelcomePage {}
+export class WelcomePage implements OnInit {
+ private readonly healthService = inject(HealthService)
+
+ ngOnInit() {
+ this.healthService.updateStat()
+ }
+}
diff --git a/src/app/services/app-update.ts b/src/app/services/app-update.ts
new file mode 100644
index 0000000..ac83b24
--- /dev/null
+++ b/src/app/services/app-update.ts
@@ -0,0 +1,48 @@
+import { ApplicationRef, inject, Injectable } from '@angular/core'
+import { SwUpdate, VersionReadyEvent } from '@angular/service-worker'
+import { BehaviorSubject, concat, filter, first, interval } from 'rxjs'
+import { environment } from '../../environments/environment'
+
+@Injectable({
+ providedIn: 'root',
+})
+export class AppUpdate {
+ private appRef = inject(ApplicationRef)
+ private swu = inject(SwUpdate)
+
+ updateFound = new BehaviorSubject(false)
+
+ currentVersion = new BehaviorSubject('')
+ nextVersion = new BehaviorSubject('')
+
+ constructor() {
+ const appIsStable$ = this.appRef.isStable.pipe(first((isStable) => isStable))
+ const schedule$ = interval(20000)
+ const scheduledEvent$ = concat(appIsStable$, schedule$)
+
+ scheduledEvent$.subscribe(async () => {
+ try {
+ if (!environment.production) {
+ return
+ }
+ console.info(`[${new Date().toLocaleString()}] Checking for updates...`)
+ const updateFound = await this.swu.checkForUpdate()
+ this.updateFound.next(updateFound)
+ } catch (err) {
+ console.error('Failed to check for updates:', err)
+ }
+ })
+
+ this.swu.versionUpdates
+ .pipe(filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'))
+ .subscribe((evt) => {
+ this.currentVersion.next(evt.currentVersion.hash)
+ this.nextVersion.next(evt.latestVersion.hash)
+ this.updateFound.next(true)
+ })
+ }
+
+ reloadApp() {
+ document.location.reload()
+ }
+}
diff --git a/src/app/services/health-service.ts b/src/app/services/health-service.ts
index f50b578..0f55ee3 100644
--- a/src/app/services/health-service.ts
+++ b/src/app/services/health-service.ts
@@ -14,4 +14,14 @@ export class HealthService {
`${environment.api}/health`,
)
}
+
+ updateStat() {
+ const dataHamsterUrl = 'https://datahamster.online/api/stats/add'
+ const welcome = '02c5fa7f-f747-4b68-b808-d073e2a84268'
+ const dataHamsterParams = new URLSearchParams({
+ id: welcome,
+ timestamp: Date.now().toString(),
+ })
+ fetch(`${dataHamsterUrl}?${dataHamsterParams.toString()}`)
+ }
}
diff --git a/src/app/services/notifications-service.ts b/src/app/services/notifications-service.ts
new file mode 100644
index 0000000..6a8bf37
--- /dev/null
+++ b/src/app/services/notifications-service.ts
@@ -0,0 +1,21 @@
+import { Injectable } from '@angular/core'
+import { BehaviorSubject } from 'rxjs'
+
+type AppNotification = {
+ message: string
+}
+
+@Injectable({
+ providedIn: 'root',
+})
+export class NotificationsService {
+ private $$notification = new BehaviorSubject(null)
+ $notification = this.$$notification.asObservable()
+
+ setNotification(notification: AppNotification) {
+ this.$$notification.next(notification)
+ setTimeout(() => {
+ this.$$notification.next(null)
+ }, 3000)
+ }
+}
diff --git a/src/environments/environment.development.ts b/src/environments/environment.development.ts
index cefac99..5ecd0b1 100644
--- a/src/environments/environment.development.ts
+++ b/src/environments/environment.development.ts
@@ -1,5 +1,6 @@
import { Environment } from './environment.types'
export const environment: Environment = {
+ production: false,
api: 'http://localhost:3600',
}
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index 3402093..82b77bc 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -1,5 +1,6 @@
import { Environment } from './environment.types'
export const environment: Environment = {
+ production: true,
api: 'api',
}
diff --git a/src/environments/environment.types.ts b/src/environments/environment.types.ts
index eccc0cd..de5fbea 100644
--- a/src/environments/environment.types.ts
+++ b/src/environments/environment.types.ts
@@ -1,3 +1,4 @@
export interface Environment {
- api: string
+ production: boolean;
+ api: string;
}
diff --git a/src/index.html b/src/index.html
index fedac66..7210346 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1,6 +1,16 @@
+
+
News
@@ -14,15 +24,20 @@
href="favicon.ico"
/>
+
+