feat: add primary badge selection for user profiles

Implements #49 - Allow users to select one primary badge to display
on their profile instead of showing all badges at once.

Changes:
- Add PrimaryBadge enum to Prisma schema and shared types (STAFF, MOD, VIP, DISCORD)
- Add primaryBadge field to User model and all user interfaces
- Update settings component with badge selection dropdown
- Only show badges the user actually has in the dropdown
- Update profile component to display only selected badge (or all if none selected)
- Add primaryBadge to admin profile edit modal
- Update API routes and services to handle primaryBadge
- Export PrimaryBadge enum from shared-types (not just as type)

Additional fixes:
- Fix Angular output naming: rename onEdit/onDelete to edit/delete
- Update all parent components using comment-display outputs
- Add type casting for Prisma PrimaryBadge enum to shared-types enum
This commit is contained in:
2026-02-19 20:28:23 -08:00
committed by Naomi Carrigan
parent bbc3b040d0
commit 9d965808a7
16 changed files with 155 additions and 36 deletions
@@ -14,7 +14,7 @@ import { CommentsService } from '../../services/comments.service';
import { UserService } from '../../services/user.service';
import { AuthService } from '../../services/auth.service';
import { ToastService } from '../../services/toast.service';
import { ProfileReportWithUsers, CommentReportWithDetails, ReportStatus, ReportReason } from '@library/shared-types';
import { ProfileReportWithUsers, CommentReportWithDetails, ReportStatus, ReportReason, PrimaryBadge } from '@library/shared-types';
@Component({
selector: 'app-admin-reports',
@@ -674,6 +674,31 @@ import { ProfileReportWithUsers, CommentReportWithDetails, ReportStatus, ReportR
></textarea>
<small class="form-help">{{ (profileEditForm.bio.length || 0) }} / 500 characters</small>
</div>
<div class="form-group">
<label for="primaryBadge">Primary Badge</label>
<select
id="primaryBadge"
name="primaryBadge"
[(ngModel)]="profileEditForm.primaryBadge"
class="form-control"
>
<option [ngValue]="undefined">None (show all badges)</option>
@if (editingProfile()?.profile?.badges.isStaff) {
<option [ngValue]="PrimaryBadge.STAFF">Staff</option>
}
@if (editingProfile()?.profile?.badges.isMod) {
<option [ngValue]="PrimaryBadge.MOD">Moderator</option>
}
@if (editingProfile()?.profile?.badges.isVip) {
<option [ngValue]="PrimaryBadge.VIP">VIP</option>
}
@if (editingProfile()?.profile?.badges.inDiscord) {
<option [ngValue]="PrimaryBadge.DISCORD">Discord Member</option>
}
</select>
<small class="form-help">Choose one badge to display on profile, or show all</small>
</div>
</div>
<div class="form-section">
@@ -1462,8 +1487,9 @@ export class AdminReportsComponent implements OnInit {
private toastService = inject(ToastService);
private router = inject(Router);
// Make ReportStatus accessible in template
// Make ReportStatus and PrimaryBadge accessible in template
protected readonly ReportStatus = ReportStatus;
protected readonly PrimaryBadge = PrimaryBadge;
reportType = signal<'profile' | 'comment'>('profile');
allProfileReports = signal<ProfileReportWithUsers[]>([]);
@@ -1487,6 +1513,7 @@ export class AdminReportsComponent implements OnInit {
slug: '',
bio: '',
profilePublic: true,
primaryBadge: undefined as PrimaryBadge | undefined,
website: '',
discordServer: '',
bluesky: '',
@@ -1773,6 +1800,7 @@ export class AdminReportsComponent implements OnInit {
slug: profile.slug || '',
bio: profile.bio || '',
profilePublic: true, // We'll get this from the full user object if needed
primaryBadge: profile.primaryBadge || undefined,
website: profile.website || '',
discordServer: profile.discordServer || '',
bluesky: profile.bluesky || '',
@@ -1798,6 +1826,7 @@ export class AdminReportsComponent implements OnInit {
slug: '',
bio: '',
profilePublic: true,
primaryBadge: undefined as PrimaryBadge | undefined,
website: '',
discordServer: '',
bluesky: '',