feat: Multiple Features, Accessibility, Security, and UX Improvements #59

Merged
naomi merged 27 commits from feat/polish into main 2026-02-20 01:51:25 -08:00
7 changed files with 132 additions and 1 deletions
Showing only changes of commit fa331df203 - Show all commits
+5
View File
@@ -34,6 +34,7 @@ model Game {
links Link[] links Link[]
series String? series String?
seriesOrder Int? @db.Int seriesOrder Int? @db.Int
timeSpent Int? @db.Int
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
comments Comment[] comments Comment[]
@@ -62,6 +63,7 @@ model Book {
links Link[] links Link[]
series String? series String?
seriesOrder Int? @db.Int seriesOrder Int? @db.Int
timeSpent Int? @db.Int
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
comments Comment[] comments Comment[]
@@ -89,6 +91,7 @@ model Music {
coverArt String? coverArt String?
tags String[] tags String[]
links Link[] links Link[]
timeSpent Int? @db.Int
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
comments Comment[] comments Comment[]
@@ -135,6 +138,7 @@ model Show {
coverImage String? coverImage String?
tags String[] tags String[]
links Link[] links Link[]
timeSpent Int? @db.Int
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
comments Comment[] comments Comment[]
@@ -168,6 +172,7 @@ model Manga {
coverImage String? coverImage String?
tags String[] tags String[]
links Link[] links Link[]
timeSpent Int? @db.Int
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
comments Comment[] comments Comment[]
@@ -104,6 +104,34 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
> >
</div> </div>
<div class="form-row">
<div class="form-group">
<label for="timeHours">Time Spent (Hours)</label>
<input
type="number"
id="timeHours"
[(ngModel)]="newGameTimeHours"
name="timeHours"
min="0"
placeholder="0"
(ngModelChange)="updateNewGameTimeSpent()"
>
</div>
<div class="form-group">
<label for="timeMinutes">Time Spent (Minutes)</label>
<input
type="number"
id="timeMinutes"
[(ngModel)]="newGameTimeMinutes"
name="timeMinutes"
min="0"
max="59"
placeholder="0"
(ngModelChange)="updateNewGameTimeSpent()"
>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="notes">Notes</label> <label for="notes">Notes</label>
<textarea <textarea
@@ -277,6 +305,34 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
> >
</div> </div>
<div class="form-row">
<div class="form-group">
<label for="edit-timeHours">Time Spent (Hours)</label>
<input
type="number"
id="edit-timeHours"
[(ngModel)]="editGameTimeHours"
name="timeHours"
min="0"
placeholder="0"
(ngModelChange)="updateEditGameTimeSpent()"
>
</div>
<div class="form-group">
<label for="edit-timeMinutes">Time Spent (Minutes)</label>
<input
type="number"
id="edit-timeMinutes"
[(ngModel)]="editGameTimeMinutes"
name="timeMinutes"
min="0"
max="59"
placeholder="0"
(ngModelChange)="updateEditGameTimeSpent()"
>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="edit-notes">Notes</label> <label for="edit-notes">Notes</label>
<textarea <textarea
@@ -574,6 +630,12 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
</div> </div>
} }
@if (game.timeSpent) {
<p class="time-spent">
⏱️ Time Played: {{ formatTimeSpent(game.timeSpent) }}
</p>
}
<app-like-button <app-like-button
entityType="game" entityType="game"
[entityId]="game.id" [entityId]="game.id"
@@ -741,6 +803,13 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
font-size: 1rem; font-size: 1rem;
} }
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.form-actions { .form-actions {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
@@ -942,6 +1011,13 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
margin: 0.5rem 0; margin: 0.5rem 0;
} }
.time-spent {
font-size: 0.9rem;
color: #10b981;
font-weight: 500;
margin: 0.5rem 0;
}
.date-started, .date-started,
.date-finished, .date-finished,
.date-added, .date-added,
@@ -1369,6 +1445,12 @@ export class GamesListComponent implements OnInit {
editGame: Partial<UpdateGameDto> = {}; editGame: Partial<UpdateGameDto> = {};
// Time tracking state
newGameTimeHours = 0;
newGameTimeMinutes = 0;
editGameTimeHours = 0;
editGameTimeMinutes = 0;
// Tags and links input state // Tags and links input state
newTagInput = ''; newTagInput = '';
editTagInput = ''; editTagInput = '';
@@ -1461,6 +1543,8 @@ export class GamesListComponent implements OnInit {
tags: [], tags: [],
links: [] links: []
}; };
this.newGameTimeHours = 0;
this.newGameTimeMinutes = 0;
this.newGameImagePreview.set(null); this.newGameImagePreview.set(null);
this.imageError.set(null); this.imageError.set(null);
this.newTagInput = ''; this.newTagInput = '';
@@ -1468,6 +1552,16 @@ export class GamesListComponent implements OnInit {
this.newLinkUrl = ''; this.newLinkUrl = '';
} }
updateNewGameTimeSpent() {
const totalMinutes = (this.newGameTimeHours * 60) + this.newGameTimeMinutes;
this.newGame.timeSpent = totalMinutes > 0 ? totalMinutes : undefined;
}
updateEditGameTimeSpent() {
const totalMinutes = (this.editGameTimeHours * 60) + this.editGameTimeMinutes;
this.editGame.timeSpent = totalMinutes > 0 ? totalMinutes : undefined;
}
addTag(target: 'new' | 'edit') { addTag(target: 'new' | 'edit') {
const input = target === 'new' ? this.newTagInput.trim() : this.editTagInput.trim(); const input = target === 'new' ? this.newTagInput.trim() : this.editTagInput.trim();
if (!input) return; if (!input) return;
@@ -1557,8 +1651,17 @@ export class GamesListComponent implements OnInit {
tags: [...(game.tags || [])], tags: [...(game.tags || [])],
links: [...(game.links || [])], links: [...(game.links || [])],
series: game.series, series: game.series,
seriesOrder: game.seriesOrder seriesOrder: game.seriesOrder,
timeSpent: game.timeSpent
}; };
// Populate time fields from existing timeSpent
if (game.timeSpent) {
this.editGameTimeHours = Math.floor(game.timeSpent / 60);
this.editGameTimeMinutes = game.timeSpent % 60;
} else {
this.editGameTimeHours = 0;
this.editGameTimeMinutes = 0;
}
this.editGameImagePreview.set(game.coverImage || null); this.editGameImagePreview.set(game.coverImage || null);
this.showAddForm.set(false); this.showAddForm.set(false);
this.imageError.set(null); this.imageError.set(null);
@@ -1650,6 +1753,19 @@ export class GamesListComponent implements OnInit {
return new Date(date).toLocaleDateString(); return new Date(date).toLocaleDateString();
} }
formatTimeSpent(minutes: number): string {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
if (hours === 0) {
return `${mins}m`;
} else if (mins === 0) {
return `${hours}h`;
} else {
return `${hours}h ${mins}m`;
}
}
toggleComments(gameId: string) { toggleComments(gameId: string) {
const expanded = this.expandedComments(); const expanded = this.expandedComments();
const isCurrentlyExpanded = expanded[gameId]; const isCurrentlyExpanded = expanded[gameId];
+2
View File
@@ -29,6 +29,7 @@ interface Book {
links: Array<Link>; links: Array<Link>;
series?: string; series?: string;
seriesOrder?: number; seriesOrder?: number;
timeSpent?: number;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
} }
@@ -47,6 +48,7 @@ interface CreateBookDto {
links?: Array<Link>; links?: Array<Link>;
series?: string; series?: string;
seriesOrder?: number; seriesOrder?: number;
timeSpent?: number;
} }
interface UpdateBookDto extends Partial<CreateBookDto> { interface UpdateBookDto extends Partial<CreateBookDto> {
+2
View File
@@ -29,6 +29,7 @@ interface Game {
links: Array<Link>; links: Array<Link>;
series?: string; series?: string;
seriesOrder?: number; seriesOrder?: number;
timeSpent?: number;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
} }
@@ -46,6 +47,7 @@ interface CreateGameDto {
links?: Array<Link>; links?: Array<Link>;
series?: string; series?: string;
seriesOrder?: number; seriesOrder?: number;
timeSpent?: number;
} }
interface UpdateGameDto extends Partial<CreateGameDto> { interface UpdateGameDto extends Partial<CreateGameDto> {
+2
View File
@@ -27,6 +27,7 @@ interface Manga {
coverImage?: string; coverImage?: string;
tags: Array<string>; tags: Array<string>;
links: Array<Link>; links: Array<Link>;
timeSpent?: number;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
} }
@@ -42,6 +43,7 @@ interface CreateMangaDto {
coverImage?: string; coverImage?: string;
tags?: Array<string>; tags?: Array<string>;
links?: Array<Link>; links?: Array<Link>;
timeSpent?: number;
} }
interface UpdateMangaDto extends Partial<CreateMangaDto> { interface UpdateMangaDto extends Partial<CreateMangaDto> {
+2
View File
@@ -34,6 +34,7 @@ interface Music {
coverArt?: string; coverArt?: string;
tags: Array<string>; tags: Array<string>;
links: Array<Link>; links: Array<Link>;
timeSpent?: number;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
} }
@@ -50,6 +51,7 @@ interface CreateMusicDto {
coverArt?: string; coverArt?: string;
tags?: Array<string>; tags?: Array<string>;
links?: Array<Link>; links?: Array<Link>;
timeSpent?: number;
} }
interface UpdateMusicDto extends Partial<CreateMusicDto> { interface UpdateMusicDto extends Partial<CreateMusicDto> {
+2
View File
@@ -34,6 +34,7 @@ interface Show {
coverImage?: string; coverImage?: string;
tags: Array<string>; tags: Array<string>;
links: Array<Link>; links: Array<Link>;
timeSpent?: number;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
} }
@@ -49,6 +50,7 @@ interface CreateShowDto {
coverImage?: string; coverImage?: string;
tags?: Array<string>; tags?: Array<string>;
links?: Array<Link>; links?: Array<Link>;
timeSpent?: number;
} }
interface UpdateShowDto extends Partial<CreateShowDto> { interface UpdateShowDto extends Partial<CreateShowDto> {