generated from nhcarrigan/template
feat: initial prototype works
I can log in and create a book! Woo!
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
/**
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { Component, OnInit, inject, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { GamesService } from '../../services/games.service';
|
||||
import { BooksService } from '../../services/books.service';
|
||||
import { MusicService } from '../../services/music.service';
|
||||
import { Game, GameStatus, Book, BookStatus, Music, MusicType } from '@library/shared-types';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterModule],
|
||||
template: `
|
||||
<div class="container">
|
||||
<div class="hero">
|
||||
<h1>Welcome to Naomi's Library</h1>
|
||||
<p class="tagline">A personal collection of games, books, and music</p>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<a routerLink="/games" class="stat-card games-card">
|
||||
<div class="icon">🎮</div>
|
||||
<div class="stat-info">
|
||||
<h3>Games</h3>
|
||||
<div class="count">{{ gamesCount() }}</div>
|
||||
<p>{{ currentlyPlayingCount() }} currently playing</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a routerLink="/books" class="stat-card books-card">
|
||||
<div class="icon">📚</div>
|
||||
<div class="stat-info">
|
||||
<h3>Books</h3>
|
||||
<div class="count">{{ booksCount() }}</div>
|
||||
<p>{{ currentlyReadingCount() }} currently reading</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a routerLink="/music" class="stat-card music-card">
|
||||
<div class="icon">🎵</div>
|
||||
<div class="stat-info">
|
||||
<h3>Music</h3>
|
||||
<div class="count">{{ musicCount() }}</div>
|
||||
<p>{{ albumsCount() }} albums, {{ singlesCount() }} singles</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="recent-section">
|
||||
<h2>Recent Additions</h2>
|
||||
|
||||
<div class="recent-grid">
|
||||
@if (recentGames().length > 0) {
|
||||
<div class="recent-category">
|
||||
<h3>🎮 Latest Games</h3>
|
||||
<ul class="recent-list">
|
||||
@for (game of recentGames(); track game.id) {
|
||||
<li>
|
||||
<a routerLink="/games">{{ game.title }}</a>
|
||||
@if (game.platform) {
|
||||
<span class="platform">({{ game.platform }})</span>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recentBooks().length > 0) {
|
||||
<div class="recent-category">
|
||||
<h3>📚 Latest Books</h3>
|
||||
<ul class="recent-list">
|
||||
@for (book of recentBooks(); track book.id) {
|
||||
<li>
|
||||
<a routerLink="/books">{{ book.title }}</a>
|
||||
<span class="author">by {{ book.author }}</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recentMusic().length > 0) {
|
||||
<div class="recent-category">
|
||||
<h3>🎵 Latest Music</h3>
|
||||
<ul class="recent-list">
|
||||
@for (music of recentMusic(); track music.id) {
|
||||
<li>
|
||||
<a routerLink="/music">{{ music.title }}</a>
|
||||
<span class="artist">by {{ music.artist }}</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 3rem 0;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--witch-purple);
|
||||
}
|
||||
|
||||
.tagline {
|
||||
font-size: 1.2rem;
|
||||
color: var(--witch-plum);
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
padding: 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border: 2px solid var(--witch-mauve);
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: all 0.3s;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 16px var(--witch-shadow);
|
||||
background: var(--witch-moon);
|
||||
}
|
||||
|
||||
.games-card:hover { border-color: var(--witch-rose); }
|
||||
.books-card:hover { border-color: var(--witch-plum); }
|
||||
.music-card:hover { border-color: var(--witch-purple); }
|
||||
|
||||
.icon {
|
||||
font-size: 3rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stat-info h3 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
color: var(--witch-plum);
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: 2.5rem;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.games-card .count { color: var(--witch-rose); }
|
||||
.books-card .count { color: var(--witch-plum); }
|
||||
.music-card .count { color: var(--witch-purple); }
|
||||
|
||||
.stat-info p {
|
||||
margin: 0;
|
||||
color: var(--witch-plum);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.recent-section {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
border: 2px solid var(--witch-lavender);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.recent-section h2 {
|
||||
margin: 0 0 1.5rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.recent-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.recent-category h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.1rem;
|
||||
color: var(--witch-purple);
|
||||
}
|
||||
|
||||
.recent-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.recent-list li {
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid var(--witch-lavender);
|
||||
}
|
||||
|
||||
.recent-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.recent-list a {
|
||||
color: var(--witch-rose);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.recent-list a:hover {
|
||||
color: var(--witch-plum);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.platform,
|
||||
.author,
|
||||
.artist {
|
||||
display: block;
|
||||
font-size: 0.85rem;
|
||||
color: var(--witch-plum);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class HomeComponent implements OnInit {
|
||||
gamesService = inject(GamesService);
|
||||
booksService = inject(BooksService);
|
||||
musicService = inject(MusicService);
|
||||
|
||||
games = signal<Game[]>([]);
|
||||
books = signal<Book[]>([]);
|
||||
music = signal<Music[]>([]);
|
||||
|
||||
ngOnInit() {
|
||||
// Load all data
|
||||
this.gamesService.getAllGames().subscribe(games => this.games.set(games));
|
||||
this.booksService.getAllBooks().subscribe(books => this.books.set(books));
|
||||
this.musicService.getAllMusic().subscribe(music => this.music.set(music));
|
||||
}
|
||||
|
||||
// Games stats
|
||||
gamesCount() {
|
||||
return this.games().length;
|
||||
}
|
||||
|
||||
currentlyPlayingCount() {
|
||||
return this.games().filter(g => g.status === GameStatus.playing).length;
|
||||
}
|
||||
|
||||
recentGames() {
|
||||
return this.games().slice(0, 5);
|
||||
}
|
||||
|
||||
// Books stats
|
||||
booksCount() {
|
||||
return this.books().length;
|
||||
}
|
||||
|
||||
currentlyReadingCount() {
|
||||
return this.books().filter(b => b.status === BookStatus.reading).length;
|
||||
}
|
||||
|
||||
recentBooks() {
|
||||
return this.books().slice(0, 5);
|
||||
}
|
||||
|
||||
// Music stats
|
||||
musicCount() {
|
||||
return this.music().length;
|
||||
}
|
||||
|
||||
albumsCount() {
|
||||
return this.music().filter(m => m.type === MusicType.album).length;
|
||||
}
|
||||
|
||||
singlesCount() {
|
||||
return this.music().filter(m => m.type === MusicType.single).length;
|
||||
}
|
||||
|
||||
recentMusic() {
|
||||
return this.music().slice(0, 5);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user