diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma
index 581605f..2e12dd0 100644
--- a/api/prisma/schema.prisma
+++ b/api/prisma/schema.prisma
@@ -176,6 +176,13 @@ enum MangaStatus {
RETIRED
}
+enum PrimaryBadge {
+ STAFF
+ MOD
+ VIP
+ DISCORD
+}
+
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
discordId String @unique
@@ -186,6 +193,7 @@ model User {
displayName String?
bio String?
profilePublic Boolean @default(true)
+ primaryBadge PrimaryBadge?
website String?
discordServer String?
bluesky String?
diff --git a/api/src/app/routes/users/index.ts b/api/src/app/routes/users/index.ts
index c7283bc..fc94a67 100644
--- a/api/src/app/routes/users/index.ts
+++ b/api/src/app/routes/users/index.ts
@@ -5,7 +5,7 @@
*/
import { FastifyPluginAsync } from "fastify";
-import { User, AuditAction, AuditCategory } from "@library/shared-types";
+import { User, AuditAction, AuditCategory, PrimaryBadge } from "@library/shared-types";
import { UserService } from "../../services/user.service";
import { AuditService } from "../../services/audit.service";
import { adminGuard } from "../../middleware/admin-guard";
@@ -15,6 +15,7 @@ interface UpdateUserSettingsBody {
displayName?: string;
bio?: string;
profilePublic?: boolean;
+ primaryBadge?: PrimaryBadge;
website?: string;
discordServer?: string;
bluesky?: string;
@@ -31,6 +32,7 @@ interface UserProfileResponse {
avatar?: string;
bio?: string;
slug?: string;
+ primaryBadge?: PrimaryBadge;
website?: string;
discordServer?: string;
bluesky?: string;
@@ -144,6 +146,7 @@ const usersRoutes: FastifyPluginAsync = async (app) => {
avatar: profile.avatar,
bio: profile.bio,
slug: profile.slug,
+ primaryBadge: profile.primaryBadge,
website: profile.website,
discordServer: profile.discordServer,
bluesky: profile.bluesky,
diff --git a/api/src/app/services/user.service.ts b/api/src/app/services/user.service.ts
index 7540c6f..68c3dcf 100644
--- a/api/src/app/services/user.service.ts
+++ b/api/src/app/services/user.service.ts
@@ -4,7 +4,7 @@
* @author Naomi Carrigan
*/
-import { User } from "@library/shared-types";
+import { User, PrimaryBadge } from "@library/shared-types";
import { prisma } from "../lib/prisma";
import { SuggestionStatus } from "@prisma/client";
@@ -26,6 +26,7 @@ export class UserService {
displayName: user.displayName || undefined,
bio: user.bio || undefined,
profilePublic: user.profilePublic,
+ primaryBadge: (user.primaryBadge as PrimaryBadge) || undefined,
website: user.website || undefined,
discordServer: user.discordServer || undefined,
bluesky: user.bluesky || undefined,
@@ -61,6 +62,7 @@ export class UserService {
displayName: user.displayName || undefined,
bio: user.bio || undefined,
profilePublic: user.profilePublic,
+ primaryBadge: (user.primaryBadge as PrimaryBadge) || undefined,
website: user.website || undefined,
discordServer: user.discordServer || undefined,
bluesky: user.bluesky || undefined,
@@ -93,6 +95,7 @@ export class UserService {
displayName: user.displayName || undefined,
bio: user.bio || undefined,
profilePublic: user.profilePublic,
+ primaryBadge: (user.primaryBadge as PrimaryBadge) || undefined,
website: user.website || undefined,
discordServer: user.discordServer || undefined,
bluesky: user.bluesky || undefined,
@@ -125,6 +128,7 @@ export class UserService {
displayName: user.displayName || undefined,
bio: user.bio || undefined,
profilePublic: user.profilePublic,
+ primaryBadge: (user.primaryBadge as PrimaryBadge) || undefined,
website: user.website || undefined,
discordServer: user.discordServer || undefined,
bluesky: user.bluesky || undefined,
@@ -169,6 +173,7 @@ export class UserService {
displayName: user.displayName || undefined,
bio: user.bio || undefined,
profilePublic: user.profilePublic,
+ primaryBadge: (user.primaryBadge as PrimaryBadge) || undefined,
website: user.website || undefined,
discordServer: user.discordServer || undefined,
bluesky: user.bluesky || undefined,
@@ -192,6 +197,7 @@ export class UserService {
displayName?: string;
bio?: string;
profilePublic?: boolean;
+ primaryBadge?: PrimaryBadge;
website?: string;
discordServer?: string;
bluesky?: string;
@@ -216,6 +222,7 @@ export class UserService {
displayName: user.displayName || undefined,
bio: user.bio || undefined,
profilePublic: user.profilePublic,
+ primaryBadge: (user.primaryBadge as PrimaryBadge) || undefined,
website: user.website || undefined,
discordServer: user.discordServer || undefined,
bluesky: user.bluesky || undefined,
@@ -239,6 +246,7 @@ export class UserService {
avatar?: string | null;
bio?: string | null;
slug?: string | null;
+ primaryBadge?: PrimaryBadge | null;
website?: string | null;
discordServer?: string | null;
bluesky?: string | null;
@@ -294,6 +302,7 @@ export class UserService {
avatar: user.avatar,
bio: user.bio,
slug: user.slug,
+ primaryBadge: user.primaryBadge as PrimaryBadge,
website: user.website,
discordServer: user.discordServer,
bluesky: user.bluesky,
diff --git a/apps/frontend/src/app/components/admin-reports/admin-reports.component.ts b/apps/frontend/src/app/components/admin-reports/admin-reports.component.ts
index 65d21a4..9edffab 100644
--- a/apps/frontend/src/app/components/admin-reports/admin-reports.component.ts
+++ b/apps/frontend/src/app/components/admin-reports/admin-reports.component.ts
@@ -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
>
{{ (profileEditForm.bio.length || 0) }} / 500 characters
+
+
+
+
+ Choose one badge to display on profile, or show all
+
}
diff --git a/apps/frontend/src/app/components/books/books-list.component.ts b/apps/frontend/src/app/components/books/books-list.component.ts
index 0af45ac..bcdbb50 100644
--- a/apps/frontend/src/app/components/books/books-list.component.ts
+++ b/apps/frontend/src/app/components/books/books-list.component.ts
@@ -658,8 +658,8 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
} @else {
}
diff --git a/apps/frontend/src/app/components/comment-display/comment-display.component.ts b/apps/frontend/src/app/components/comment-display/comment-display.component.ts
index 879aa51..4a9f3dd 100644
--- a/apps/frontend/src/app/components/comment-display/comment-display.component.ts
+++ b/apps/frontend/src/app/components/comment-display/comment-display.component.ts
@@ -42,7 +42,7 @@ import { ReportModalComponent } from '../report-modal/report-modal.component';
}
@if (canDeleteComment(comment)) {
-
+
}
@if (canReportComment(comment)) {
@@ -245,8 +245,8 @@ export class CommentDisplayComponent {
readonly sanitizeService = inject(SanitizeService);
@Input({ required: true }) comments = signal([]);
- @Output() onEdit = new EventEmitter<{ commentId: string; content: string }>();
- @Output() onDelete = new EventEmitter();
+ @Output() edit = new EventEmitter<{ commentId: string; content: string }>();
+ @Output() delete = new EventEmitter();
editingCommentId = signal(null);
editCommentContent = '';
@@ -289,7 +289,7 @@ export class CommentDisplayComponent {
}
saveEdit(commentId: string): void {
- this.onEdit.emit({ commentId, content: this.editCommentContent });
+ this.edit.emit({ commentId, content: this.editCommentContent });
this.cancelEdit();
}
diff --git a/apps/frontend/src/app/components/games/games-list.component.ts b/apps/frontend/src/app/components/games/games-list.component.ts
index c592498..430c4f9 100644
--- a/apps/frontend/src/app/components/games/games-list.component.ts
+++ b/apps/frontend/src/app/components/games/games-list.component.ts
@@ -611,8 +611,8 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
} @else {
}
diff --git a/apps/frontend/src/app/components/manga/manga-list.component.ts b/apps/frontend/src/app/components/manga/manga-list.component.ts
index 58c9ecf..a6eafe3 100644
--- a/apps/frontend/src/app/components/manga/manga-list.component.ts
+++ b/apps/frontend/src/app/components/manga/manga-list.component.ts
@@ -613,8 +613,8 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
}
diff --git a/apps/frontend/src/app/components/music/music-list.component.ts b/apps/frontend/src/app/components/music/music-list.component.ts
index 1c91be4..4ccad38 100644
--- a/apps/frontend/src/app/components/music/music-list.component.ts
+++ b/apps/frontend/src/app/components/music/music-list.component.ts
@@ -689,8 +689,8 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
}
diff --git a/apps/frontend/src/app/components/profile/profile.component.ts b/apps/frontend/src/app/components/profile/profile.component.ts
index 62fb84e..3d3eeb5 100644
--- a/apps/frontend/src/app/components/profile/profile.component.ts
+++ b/apps/frontend/src/app/components/profile/profile.component.ts
@@ -14,6 +14,7 @@ import { UserService, UserProfileResponse } from '../../services/user.service';
import { ToastService } from '../../services/toast.service';
import { AuthService } from '../../services/auth.service';
import { ReportModalComponent } from '../report-modal/report-modal.component';
+import { PrimaryBadge } from '@library/shared-types';
@Component({
selector: 'app-profile',
@@ -58,17 +59,34 @@ import { ReportModalComponent } from '../report-modal/report-modal.component';
- @if (profile()!.badges.isStaff) {
- Staff
- }
- @if (profile()!.badges.isMod) {
- Moderator
- }
- @if (profile()!.badges.isVip) {
- VIP
- }
- @if (profile()!.badges.inDiscord) {
- Discord Member
+ @if (profile()!.primaryBadge) {
+
+ @if (profile()!.primaryBadge === PrimaryBadge.STAFF && profile()!.badges.isStaff) {
+ Staff
+ }
+ @if (profile()!.primaryBadge === PrimaryBadge.MOD && profile()!.badges.isMod) {
+ Moderator
+ }
+ @if (profile()!.primaryBadge === PrimaryBadge.VIP && profile()!.badges.isVip) {
+ VIP
+ }
+ @if (profile()!.primaryBadge === PrimaryBadge.DISCORD && profile()!.badges.inDiscord) {
+ Discord Member
+ }
+ } @else {
+
+ @if (profile()!.badges.isStaff) {
+ Staff
+ }
+ @if (profile()!.badges.isMod) {
+ Moderator
+ }
+ @if (profile()!.badges.isVip) {
+ VIP
+ }
+ @if (profile()!.badges.inDiscord) {
+ Discord Member
+ }
}
@@ -426,6 +444,9 @@ export class ProfileComponent implements OnInit {
error = signal(null);
reportModalOpen = signal(false);
+ // Expose PrimaryBadge enum for template
+ readonly PrimaryBadge = PrimaryBadge;
+
// Font Awesome icons
faGlobe = faGlobe;
faGithub = faGithub;
diff --git a/apps/frontend/src/app/components/settings/settings.component.ts b/apps/frontend/src/app/components/settings/settings.component.ts
index 49a6b22..1f6b490 100644
--- a/apps/frontend/src/app/components/settings/settings.component.ts
+++ b/apps/frontend/src/app/components/settings/settings.component.ts
@@ -10,7 +10,7 @@ import { FormsModule } from '@angular/forms';
import { UserService, UpdateUserSettingsRequest } from '../../services/user.service';
import { AuthService } from '../../services/auth.service';
import { ToastService } from '../../services/toast.service';
-import { User } from '@library/shared-types';
+import { User, PrimaryBadge } from '@library/shared-types';
@Component({
selector: 'app-settings',
@@ -69,6 +69,30 @@ import { User } from '@library/shared-types';
>
{{ (formData.bio?.length || 0) }} / 500 characters
+
+
+
+
+ Choose one badge to display on your profile, or show all
+
@@ -264,7 +288,8 @@ import { User } from '@library/shared-types';
}
.form-group input[type="text"],
- .form-group textarea {
+ .form-group textarea,
+ .form-group select {
width: 100%;
padding: 0.75rem;
border: 1px solid rgba(155, 89, 182, 0.5);
@@ -275,8 +300,13 @@ import { User } from '@library/shared-types';
font-family: inherit;
}
+ .form-group select {
+ cursor: pointer;
+ }
+
.form-group input[type="text"]:focus,
- .form-group textarea:focus {
+ .form-group textarea:focus,
+ .form-group select:focus {
outline: none;
border-color: var(--accent-colour, #9b59b6);
box-shadow: 0 0 0 2px rgba(155, 89, 182, 0.3);
@@ -375,11 +405,15 @@ export class SettingsComponent implements OnInit {
loading = signal(true);
saving = signal(false);
+ // Expose PrimaryBadge enum for template
+ readonly PrimaryBadge = PrimaryBadge;
+
formData: UpdateUserSettingsRequest & { bio?: string } = {
displayName: '',
slug: '',
bio: '',
profilePublic: true,
+ primaryBadge: undefined,
website: '',
discordServer: '',
bluesky: '',
@@ -398,6 +432,7 @@ export class SettingsComponent implements OnInit {
slug: userData.slug || '',
bio: userData.bio || '',
profilePublic: userData.profilePublic ?? true,
+ primaryBadge: userData.primaryBadge || undefined,
website: userData.website || '',
discordServer: userData.discordServer || '',
bluesky: userData.bluesky || '',
@@ -424,6 +459,7 @@ export class SettingsComponent implements OnInit {
slug: this.formData.slug || undefined,
bio: this.formData.bio || undefined,
profilePublic: this.formData.profilePublic,
+ primaryBadge: this.formData.primaryBadge || undefined,
website: this.formData.website || undefined,
discordServer: this.formData.discordServer || undefined,
bluesky: this.formData.bluesky || undefined,
diff --git a/apps/frontend/src/app/components/shows/shows-list.component.ts b/apps/frontend/src/app/components/shows/shows-list.component.ts
index ce41b07..24910d1 100644
--- a/apps/frontend/src/app/components/shows/shows-list.component.ts
+++ b/apps/frontend/src/app/components/shows/shows-list.component.ts
@@ -607,8 +607,8 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
}
diff --git a/apps/frontend/src/app/services/user.service.ts b/apps/frontend/src/app/services/user.service.ts
index 45f67d9..8b4f75f 100644
--- a/apps/frontend/src/app/services/user.service.ts
+++ b/apps/frontend/src/app/services/user.service.ts
@@ -7,7 +7,7 @@
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { ApiService } from './api.service';
-import { User } from '@library/shared-types';
+import { User, PrimaryBadge } from '@library/shared-types';
export interface UserProfileResponse {
id: string;
@@ -16,6 +16,7 @@ export interface UserProfileResponse {
avatar?: string;
bio?: string;
slug?: string;
+ primaryBadge?: PrimaryBadge;
website?: string;
discordServer?: string;
bluesky?: string;
@@ -43,6 +44,7 @@ export interface UpdateUserSettingsRequest {
displayName?: string;
bio?: string;
profilePublic?: boolean;
+ primaryBadge?: PrimaryBadge;
website?: string;
discordServer?: string;
bluesky?: string;
diff --git a/shared-types/src/index.ts b/shared-types/src/index.ts
index 69b4d61..ec404b3 100644
--- a/shared-types/src/index.ts
+++ b/shared-types/src/index.ts
@@ -5,7 +5,7 @@
*/
export type * from "./lib/art.types";
export * from "./lib/audit.types";
-export type * from "./lib/auth.types";
+export * from "./lib/auth.types";
export * from "./lib/book.types";
export type * from "./lib/comment.types";
export type * from "./lib/common.types";
diff --git a/shared-types/src/lib/auth.types.ts b/shared-types/src/lib/auth.types.ts
index a80b044..8fda2c2 100644
--- a/shared-types/src/lib/auth.types.ts
+++ b/shared-types/src/lib/auth.types.ts
@@ -4,6 +4,15 @@
* @author Naomi Carrigan
*/
+/* eslint-disable @typescript-eslint/naming-convention -- Prisma enum values use UPPER_CASE */
+enum PrimaryBadge {
+ STAFF = "STAFF",
+ MOD = "MOD",
+ VIP = "VIP",
+ DISCORD = "DISCORD",
+}
+/* eslint-enable @typescript-eslint/naming-convention */
+
interface User {
id: string;
email: string;
@@ -13,6 +22,7 @@ interface User {
displayName?: string;
bio?: string;
profilePublic: boolean;
+ primaryBadge?: PrimaryBadge;
website?: string;
discordServer?: string;
bluesky?: string;
@@ -47,4 +57,5 @@ interface AuthResponse {
user: User;
}
+export { PrimaryBadge };
export type { AuthResponse, JwtPayload, User };