From af4216f383abf3ee65c8600fce3e69e0d4d7e356 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sun, 31 May 2026 10:49:26 +0100 Subject: [PATCH] frontend: move app shell into standalone root and wire analytics service --- frontend/src/app/app.config.ts | 2 - frontend/src/app/app.html | 343 ------------------ frontend/src/app/app.scss | 0 frontend/src/app/app.ts | 5 +- .../src/app/services/analytics.service.ts | 65 ++++ 5 files changed, 69 insertions(+), 346 deletions(-) delete mode 100644 frontend/src/app/app.html delete mode 100644 frontend/src/app/app.scss create mode 100644 frontend/src/app/services/analytics.service.ts diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index b8fcf5c..8a08685 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -4,7 +4,6 @@ import { isDevMode, provideZonelessChangeDetection, } from '@angular/core'; -import { provideRouter } from '@angular/router'; import { provideHttpClient, withFetch } from '@angular/common/http'; import { provideServiceWorker } from '@angular/service-worker'; @@ -12,7 +11,6 @@ export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), provideZonelessChangeDetection(), - provideRouter([]), provideHttpClient(withFetch()), provideServiceWorker('ngsw-worker.js', { enabled: !isDevMode(), diff --git a/frontend/src/app/app.html b/frontend/src/app/app.html deleted file mode 100644 index 0edf11a..0000000 --- a/frontend/src/app/app.html +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title() }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'Prompt and best practices for AI', link: 'https://angular.dev/ai/develop-with-ai'}, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - diff --git a/frontend/src/app/app.scss b/frontend/src/app/app.scss deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/app/app.ts b/frontend/src/app/app.ts index e6bb0c3..ec56fbb 100644 --- a/frontend/src/app/app.ts +++ b/frontend/src/app/app.ts @@ -1,5 +1,6 @@ import { Component, ChangeDetectionStrategy, OnInit, inject } from '@angular/core'; import { StoreService } from './services/store.service'; +import { AnalyticsService } from './services/analytics.service'; import { PagesComponent } from './components/pages/pages.component'; @Component({ @@ -11,8 +12,10 @@ import { PagesComponent } from './components/pages/pages.component'; }) export class App implements OnInit { private readonly store = inject(StoreService); + private readonly analytics = inject(AnalyticsService); ngOnInit(): void { - this.store.init(); + this.analytics.init(); + void this.store.init(); } } diff --git a/frontend/src/app/services/analytics.service.ts b/frontend/src/app/services/analytics.service.ts new file mode 100644 index 0000000..84d3e0c --- /dev/null +++ b/frontend/src/app/services/analytics.service.ts @@ -0,0 +1,65 @@ +import { Injectable, isDevMode } from '@angular/core'; +import { + init as plausibleInit, + track as plausibleTrack, + type PlausibleEventOptions, +} from '@plausible-analytics/tracker'; + +const ANALYTICS_AUTO_CAPTURE_PAGEVIEWS = true; +const ANALYTICS_DOMAIN = 'schmelczer.dev/towers'; +const ANALYTICS_ENDPOINT = 'https://stats.schmelczer.dev/status'; + +@Injectable({ providedIn: 'root' }) +export class AnalyticsService { + private isInitialized = false; + private hasTrackedStart = false; + + init(): void { + if (this.isInitialized) return; + try { + plausibleInit({ + domain: ANALYTICS_DOMAIN, + endpoint: ANALYTICS_ENDPOINT, + autoCapturePageviews: ANALYTICS_AUTO_CAPTURE_PAGEVIEWS, + logging: isDevMode(), + }); + this.isInitialized = true; + } catch (error) { + console.warn('Could not initialize analytics.', error); + } + } + + private track(eventName: string, options: PlausibleEventOptions = {}): void { + try { + plausibleTrack(eventName, options); + } catch (error) { + console.warn(`Could not track analytics event "${eventName}".`, error); + } + } + + trackStart(): void { + if (this.hasTrackedStart) return; + this.hasTrackedStart = true; + this.track('Start'); + } + + trackExampleLoaded(): void { + this.track('Example Loaded'); + } + + trackPageCreated(): void { + this.track('Page Created'); + } + + trackTowerCreated(): void { + this.track('Tower Created'); + } + + trackBlockCreated({ isDone }: { isDone: boolean }): void { + this.track('Block Created', { props: { isDone: String(isDone) } }); + } + + trackBlockCompleted(): void { + this.track('Block Completed'); + } +}