feat: implement user profiles with achievements and primary badge system #58

Merged
naomi merged 17 commits from feat/user-profiles into main 2026-02-19 22:21:18 -08:00
3 changed files with 44 additions and 19 deletions
Showing only changes of commit 18cfe16d87 - Show all commits
+2 -1
View File
@@ -4,7 +4,7 @@
* @author Naomi Carrigan
*/
import { Comment, CreateCommentDto } from "@library/shared-types";
import { Comment, CreateCommentDto, PrimaryBadge } from "@library/shared-types";
import { prisma } from "../lib/prisma";
import createDOMPurify from "dompurify";
import { JSDOM } from "jsdom";
@@ -65,6 +65,7 @@ export class CommentService {
id: comment.user.id,
username: comment.user.username,
avatar: comment.user.avatar || undefined,
primaryBadge: (comment.user.primaryBadge as PrimaryBadge) || undefined,
inDiscord: comment.user.inDiscord,
isVip: comment.user.isVip,
isMod: comment.user.isMod,
@@ -7,6 +7,7 @@ import { Component, Input, Output, EventEmitter, signal, inject } from '@angular
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import type { Comment } from '@library/shared-types';
import { PrimaryBadge } from '@library/shared-types';
import { AuthService } from '../../services/auth.service';
import { SanitizeService } from '../../services/sanitize.service';
import { ReportModalComponent } from '../report-modal/report-modal.component';
@@ -25,17 +26,34 @@ import { ReportModalComponent } from '../report-modal/report-modal.component';
<img [src]="comment.user.avatar" [alt]="comment.user.username" class="comment-avatar">
}
<span class="comment-author">{{ comment.user.username }}</span>
@if (comment.user.inDiscord) {
<span class="discord-badge">Discord</span>
}
@if (comment.user.isVip) {
<span class="vip-badge">VIP</span>
}
@if (comment.user.isMod) {
<span class="mod-badge">Mod</span>
}
@if (comment.user.isStaff) {
<span class="staff-badge">Staff</span>
@if (comment.user.primaryBadge) {
<!-- Show only the selected primary badge -->
@if (comment.user.primaryBadge === PrimaryBadge.STAFF && comment.user.isStaff) {
<span class="staff-badge">Staff</span>
}
@if (comment.user.primaryBadge === PrimaryBadge.MOD && comment.user.isMod) {
<span class="mod-badge">Mod</span>
}
@if (comment.user.primaryBadge === PrimaryBadge.VIP && comment.user.isVip) {
<span class="vip-badge">VIP</span>
}
@if (comment.user.primaryBadge === PrimaryBadge.DISCORD && comment.user.inDiscord) {
<span class="discord-badge">Discord</span>
}
} @else {
<!-- Show all badges if no primary badge is selected -->
@if (comment.user.isStaff) {
<span class="staff-badge">Staff</span>
}
@if (comment.user.isMod) {
<span class="mod-badge">Mod</span>
}
@if (comment.user.isVip) {
<span class="vip-badge">VIP</span>
}
@if (comment.user.inDiscord) {
<span class="discord-badge">Discord</span>
}
}
<span class="comment-date">{{ formatDate(comment.createdAt) }}</span>
@if (canEditComment(comment)) {
@@ -244,6 +262,9 @@ export class CommentDisplayComponent {
private readonly authService = inject(AuthService);
readonly sanitizeService = inject(SanitizeService);
// Expose PrimaryBadge enum for template
readonly PrimaryBadge = PrimaryBadge;
@Input({ required: true }) comments = signal<Comment[]>([]);
@Output() edit = new EventEmitter<{ commentId: string; content: string }>();
@Output() delete = new EventEmitter<string>();
+10 -7
View File
@@ -4,14 +4,17 @@
* @author Naomi Carrigan
*/
import { PrimaryBadge } from "./auth.types";
interface CommentUser {
id: string;
username: string;
avatar?: string;
inDiscord?: boolean;
isVip?: boolean;
isMod?: boolean;
isStaff?: boolean;
id: string;
username: string;
avatar?: string;
primaryBadge?: PrimaryBadge;
inDiscord?: boolean;
isVip?: boolean;
isMod?: boolean;
isStaff?: boolean;
}
interface Comment {