generated from nhcarrigan/template
167 lines
4.2 KiB
TypeScript
167 lines
4.2 KiB
TypeScript
/**
|
|
* @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: `
|
|
<div class="like-container">
|
|
<button
|
|
type="button"
|
|
class="like-button"
|
|
[class.liked]="liked()"
|
|
[disabled]="loading() || !isAuthenticated()"
|
|
(click)="toggleLike()"
|
|
[title]="getTitle()"
|
|
>
|
|
<span class="heart-icon">{{ liked() ? '❤️' : '🤍' }}</span>
|
|
<span class="like-count">{{ count() }}</span>
|
|
</button>
|
|
</div>
|
|
`,
|
|
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';
|
|
}
|
|
} |