From e5b15e02de1a75f979eb809dab302b7ec638973f Mon Sep 17 00:00:00 2001 From: Naomi Carrigan Date: Wed, 4 Feb 2026 14:47:34 -0800 Subject: [PATCH] feat: analytics and ads --- apps/frontend/src/app/app.html | 1 + apps/frontend/src/app/app.ts | 13 +- .../app/components/footer/footer.component.ts | 138 ++++++++++++++++++ .../src/app/services/analytics.service.ts | 42 ++++++ apps/frontend/src/index.html | 14 +- 5 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 apps/frontend/src/app/components/footer/footer.component.ts create mode 100644 apps/frontend/src/app/services/analytics.service.ts diff --git a/apps/frontend/src/app/app.html b/apps/frontend/src/app/app.html index b6a4dae..4bdc0fb 100644 --- a/apps/frontend/src/app/app.html +++ b/apps/frontend/src/app/app.html @@ -2,3 +2,4 @@
+ diff --git a/apps/frontend/src/app/app.ts b/apps/frontend/src/app/app.ts index 8af4875..88e48d6 100644 --- a/apps/frontend/src/app/app.ts +++ b/apps/frontend/src/app/app.ts @@ -1,13 +1,20 @@ -import { Component } from '@angular/core'; +import { Component, inject, OnInit } from '@angular/core'; import { RouterModule } from '@angular/router'; import { HeaderComponent } from './components/header/header.component'; +import { FooterComponent } from './components/footer/footer.component'; +import { AnalyticsService } from './services/analytics.service'; @Component({ - imports: [RouterModule, HeaderComponent], + imports: [RouterModule, HeaderComponent, FooterComponent], selector: 'app-root', templateUrl: './app.html', styleUrl: './app.scss', }) -export class App { +export class App implements OnInit { protected title = 'Naomi\'s Library'; + private analytics = inject(AnalyticsService); + + ngOnInit(): void { + this.analytics.initialise(); + } } diff --git a/apps/frontend/src/app/components/footer/footer.component.ts b/apps/frontend/src/app/components/footer/footer.component.ts new file mode 100644 index 0000000..478fb26 --- /dev/null +++ b/apps/frontend/src/app/components/footer/footer.component.ts @@ -0,0 +1,138 @@ +/** + * @copyright 2026 NHCarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-footer', + standalone: true, + imports: [CommonModule], + template: ` +
+
+
+
+

Join the Community

+

Want to chat about what we're reading, playing, or listening to? Got recommendations? Come hang out!

+ + Join our Discord + +
+ +
+

Support Naomi

+

Enjoying the vibes? Your support helps keep the servers running and the coffee flowing!

+ + Buy us a coffee + +
+
+ +
+

© {{ currentYear }} NHCarrigan. Made with love.

+
+
+
+ `, + styles: [` + .footer { + background: linear-gradient(135deg, var(--witch-purple) 0%, var(--witch-plum) 100%); + color: var(--witch-moon); + padding: 3rem 2rem 1.5rem; + margin-top: 4rem; + } + + .footer-content { + max-width: 1200px; + margin: 0 auto; + } + + .cta-section { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 2rem; + margin-bottom: 2rem; + } + + .cta-card { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border-radius: 12px; + padding: 1.5rem; + text-align: center; + border: 2px solid rgba(255, 255, 255, 0.2); + transition: transform 0.3s, box-shadow 0.3s; + } + + .cta-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); + } + + .cta-card h3 { + margin: 0 0 0.75rem 0; + font-size: 1.25rem; + color: var(--witch-moon); + } + + .cta-card p { + margin: 0 0 1.25rem 0; + font-size: 0.95rem; + line-height: 1.5; + color: var(--witch-lavender); + } + + .btn { + display: inline-block; + padding: 0.75rem 1.5rem; + border-radius: 8px; + text-decoration: none; + font-weight: 600; + font-size: 1rem; + transition: all 0.3s; + } + + .btn:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + + .btn-discord { + background: #5865F2; + color: white; + } + + .btn-discord:hover { + background: #4752C4; + } + + .btn-donate { + background: var(--witch-rose); + color: var(--witch-moon); + } + + .btn-donate:hover { + background: var(--witch-mauve); + color: var(--witch-purple); + } + + .footer-bottom { + text-align: center; + padding-top: 1.5rem; + border-top: 1px solid rgba(255, 255, 255, 0.2); + } + + .footer-bottom p { + margin: 0; + font-size: 0.9rem; + color: var(--witch-lavender); + } + `] +}) +export class FooterComponent { + currentYear = new Date().getFullYear(); +} diff --git a/apps/frontend/src/app/services/analytics.service.ts b/apps/frontend/src/app/services/analytics.service.ts new file mode 100644 index 0000000..b0f8adb --- /dev/null +++ b/apps/frontend/src/app/services/analytics.service.ts @@ -0,0 +1,42 @@ +/** + * @copyright 2026 NHCarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { Injectable, inject } from '@angular/core'; +import { Router, NavigationEnd } from '@angular/router'; +import { filter } from 'rxjs/operators'; + +declare global { + interface Window { + plausible?: (event: string, options?: { props?: Record }) => void; + } +} + +@Injectable({ + providedIn: 'root' +}) +export class AnalyticsService { + private router = inject(Router); + + initialise(): void { + this.router.events.pipe( + filter((event): event is NavigationEnd => event instanceof NavigationEnd) + ).subscribe((event) => { + this.trackPageView(event.urlAfterRedirects); + }); + } + + private trackPageView(path: string): void { + if (window.plausible) { + window.plausible('pageview', { + props: { + domain: 'library.nhcarrigan.com', + page: 'Naomi\'s Library', + path: path || '/' + } + }); + } + } +} diff --git a/apps/frontend/src/index.html b/apps/frontend/src/index.html index a415161..41008e3 100644 --- a/apps/frontend/src/index.html +++ b/apps/frontend/src/index.html @@ -2,10 +2,22 @@ - frontend + Naomi's Library + + +