/** * @copyright 2026 NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import { Component, Input, inject, OnInit, signal, effect } from '@angular/core'; import { CommonModule } from '@angular/common'; import { LikesService } from '../../services/likes.service'; import { AuthService } from '../../services/auth.service'; import { Like } from '@library/shared-types'; import { take } from 'rxjs'; @Component({ selector: 'app-like-button', standalone: true, imports: [CommonModule], template: `
`, styles: [` .like-container { display: inline-flex; align-items: center; } .like-button { display: flex; align-items: center; gap: 0.5rem; padding: 0.25rem 0.75rem; border: 2px solid var(--border-color); border-radius: 9999px; background: transparent; cursor: pointer; transition: all 0.2s ease; font-size: 0.9rem; } .like-button:hover:not(:disabled) { background: var(--hover-background); transform: scale(1.05); } .like-button:disabled { cursor: not-allowed; opacity: 0.6; } .like-button.liked { border-color: var(--primary-color); background: var(--primary-light-background); } .heart-icon { font-size: 1.1rem; transition: transform 0.2s ease; } .like-button:hover:not(:disabled) .heart-icon { transform: scale(1.2); } .like-button:active:not(:disabled) .heart-icon { transform: scale(0.9); } .like-count { font-weight: 600; color: var(--text-color); } `] }) export class LikeButtonComponent implements OnInit { @Input({ required: true }) entityType!: Like['entityType']; @Input({ required: true }) entityId!: string; private likesService = inject(LikesService); private authService = inject(AuthService); liked = signal(false); count = signal(0); loading = signal(false); isAuthenticated = signal(false); ngOnInit() { // Set authentication state this.isAuthenticated.set(this.authService.isAuthenticated()); // Load initial state this.loadLikeState(); } private loadLikeState() { // Check cache first const cachedState = this.likesService.getCachedLikeState(this.entityType, this.entityId); if (cachedState) { this.liked.set(cachedState.liked); this.count.set(cachedState.count); } // Always get count (public endpoint) this.likesService.getLikeCount(this.entityType, this.entityId) .pipe(take(1)) .subscribe(count => { this.count.set(count); }); // Get user like status if authenticated if (this.isAuthenticated()) { this.likesService.getUserLikeStatus(this.entityType, this.entityId) .pipe(take(1)) .subscribe(liked => { this.liked.set(liked); }); } } toggleLike() { if (!this.isAuthenticated() || this.loading()) { return; } this.loading.set(true); // Optimistic update const newLiked = !this.liked(); const newCount = newLiked ? this.count() + 1 : Math.max(0, this.count() - 1); this.liked.set(newLiked); this.count.set(newCount); this.likesService.toggleLike(this.entityType, this.entityId) .pipe(take(1)) .subscribe({ next: (response) => { this.liked.set(response.liked); this.count.set(response.count); this.loading.set(false); }, error: () => { // Revert on error this.liked.set(!newLiked); this.count.set(newLiked ? Math.max(0, newCount - 1) : newCount + 1); this.loading.set(false); } }); } getTitle(): string { if (!this.isAuthenticated()) { return 'Sign in to like'; } return this.liked() ? 'Unlike' : 'Like'; } }