Files
kmtnflower/src/app/services/station-config.service.ts
T
robot e2356954ef
CI Pipeline / backend-test (push) Successful in 10m9s
CI Pipeline / frontend-build (push) Successful in 30s
CI Pipeline / backend-lint (push) Successful in 9s
CI Pipeline / frontend-test (push) Successful in 30s
CI Pipeline / backend-test (pull_request) Successful in 10m30s
CI Pipeline / frontend-test (pull_request) Successful in 25s
CI Pipeline / frontend-build (pull_request) Successful in 41s
CI Pipeline / backend-lint (pull_request) Successful in 9s
CI Pipeline / quality-gate (push) Successful in 3s
CI Pipeline / quality-gate (pull_request) Successful in 3s
feat(seo): Phase 3 — preload LCP image and reduce APP_INITIALIZER blocking
- Add <link rel="preload"> for hero background image (LCP element) in index.html
- Remove blocking APP_INITIALIZER for config.json and station config loading
- Config loads now fire asynchronously in App.ngOnInit() (browser only)
- SSR/prerender renders instantly with default config — no network wait
- Improves LCP, Time to First Byte, and initial paint for SEO
2026-08-06 21:29:30 +00:00

110 lines
4.1 KiB
TypeScript

import { Injectable, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import { StationConfig } from '../interfaces/station-config';
import { getAppConfig } from './app-config.service';
import { ThemeService } from './theme.service';
/**
* Hardcoded defaults — used when the API is unavailable (dev fallback).
* Rendered immediately on the initial paint so the page is never blank.
*/
const DEFAULT_CONFIG: StationConfig = {
id: 0,
key: 'default',
callsign: 'KMTN',
name_primary: 'KMountain',
name_secondary: 'Flower Radio',
tagline: 'Community Radio — Est. 1987',
frequency: '98.7 FM',
founded_year: 1987,
nonprofit_type: '501(c)(3)',
ein: '84-XXXXXXX',
logo_url: 'svg/small-flower.svg',
hero_background_url: 'svg/mountain-landscape.svg',
hero_icon_url: 'svg/orange-flower.svg',
hero_divider_url: 'svg/mountain-divider.svg',
address: '42 Pine Street\nMountain View, CO 80424',
phone: '(970) 555-0198',
email: 'hello@kmountainflower.org',
website: 'kmountainflower.org',
donation_url: '',
stream_url: '',
stream_metadata_url: '',
play_store_icon_url: '',
play_store_url: '',
app_store_embed_html: '',
// Social media
social_facebook: '',
social_twitter: '',
social_instagram: '',
social_youtube: '',
// Page content
hero_subtitle: 'Your community-powered, nonprofit radio station — bringing music, stories, and connection to the mountain towns and beyond since 1987.',
about_intro: 'For nearly four decades, KMountain Flower Radio has been the voice of our community — a nonprofit, listener-supported radio station dedicated to music, local news, and the stories that bind mountain towns together.',
about_donation_text: 'As a 501(c)(3) nonprofit, every donation keeps our airwaves free and our signal strong. Members receive exclusive perks and the satisfaction of knowing they helped make this possible.',
schedule_intro: 'All times are local (Mountain Time). Every show is free to listen to live at 98.7 FM or via our online stream.',
events_intro: 'Come meet the team, enjoy live music, and support public radio in person. All events are open to the community — friends and family of listeners welcome.',
// Color theme defaults (match _variables.scss)
color_primary: '#1a3a5c',
color_primary_light: '#2a5a8c',
color_primary_dark: '#0e2440',
color_primary_muted: '#3a6a9c',
color_accent: '#e87a2e',
color_accent_light: '#f09a4e',
color_accent_dark: '#c85a1e',
color_accent_muted: '#f0a86a',
color_background: '#faf6f0',
color_text: '#3a3632',
color_success: '#4a9e4f',
color_danger: '#c83030',
color_info: '#3a8abf',
color_white: '#ffffff',
color_light: '#f0ebe3',
color_medium: '#8a8580',
color_black: '#1a1816',
};
@Injectable({
providedIn: 'root',
})
export class StationConfigService {
private http = inject(HttpClient);
private themeService = inject(ThemeService);
private baseUrl = `${getAppConfig().apiBaseUrl}/api/station-config`;
readonly config = signal<StationConfig>(DEFAULT_CONFIG);
/** Load station config from the API. Falls back to defaults on error. */
async load(): Promise<void> {
try {
const data = await firstValueFrom(this.http.get<StationConfig>(this.baseUrl));
this.config.set(data);
this.themeService.apply(data);
} catch {
// Keep the default config if the API is unavailable.
console.warn('StationConfigService: Failed to load config, using defaults.');
}
}
/** Update station config (admin only). */
async updateConfig(payload: Partial<StationConfig>): Promise<void> {
const data = await firstValueFrom(
this.http.put<StationConfig>(this.baseUrl, payload),
);
this.config.set(data);
this.themeService.apply(data);
}
/** Upload an image file for station branding (admin only). Returns the relative URL. */
async uploadImage(file: File): Promise<string> {
const formData = new FormData();
formData.append('file', file);
const response = await firstValueFrom(
this.http.post<{ url: string }>(`${getAppConfig().apiBaseUrl}/api/storage/upload`, formData),
);
return response.url;
}
}