generated from nhcarrigan/template
fix: resolve CSP and accessibility issues (#60)
### Explanation _No response_ ### Issue _No response_ ### Attestations - [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/) - [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/). - [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/). ### Dependencies - [ ] I have pinned the dependencies to a specific patch version. ### Style - [ ] I have run the linter and resolved any errors. - [ ] My pull request uses an appropriate title, matching the conventional commit standards. - [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request. ### Tests - [ ] My contribution adds new code, and I have added tests to cover it. - [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes. - [ ] All new and existing tests pass locally with my changes. - [ ] Code coverage remains at or above the configured threshold. ### Documentation _No response_ ### Versioning _No response_ Co-authored-by: Hikari <hikari@nhcarrigan.com> Reviewed-on: #60
This commit was merged in pull request #60.
This commit is contained in:
@@ -13,9 +13,8 @@ const helmetPlugin: FastifyPluginAsync = async (app) => {
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
// Remove unsafe-inline for better security
|
||||
// Angular uses inline styles in development, but production builds should use external CSS
|
||||
styleSrc: ["'self'", process.env.NODE_ENV === "production" ? "'self'" : "'unsafe-inline'"],
|
||||
// Angular uses inline styles for component encapsulation, so we need to allow them
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
imgSrc: ["'self'", "data:", "https:"],
|
||||
scriptSrc: ["'self'"],
|
||||
connectSrc: ["'self'", process.env.FRONTEND_URL ?? "http://localhost:4200"],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { getGreeting } from "../support/app.po";
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
/**
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the greeting element from the page.
|
||||
* @returns A Cypress chainable to the h1 element.
|
||||
*/
|
||||
export const getGreeting = (): Cypress.Chainable => {
|
||||
return cy.get("h1");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
/* eslint-disable unicorn/prevent-abbreviations -- e2e is a standard Cypress filename */
|
||||
|
||||
/**
|
||||
* This example support/e2e.ts is processed and
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<a href="#main-content" class="skip-link">Skip to main content</a>
|
||||
<a href="#main-content" class="skip-link" (click)="skipToMainContent($event)">Skip to main content</a>
|
||||
<app-header></app-header>
|
||||
<main id="main-content" class="main-content" tabindex="-1">
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -1,3 +1,35 @@
|
||||
// Skip to main content link - visually hidden but accessible to screen readers
|
||||
.skip-link {
|
||||
position: absolute !important;
|
||||
width: 1px !important;
|
||||
height: 1px !important;
|
||||
padding: 0 !important;
|
||||
margin: -1px !important;
|
||||
overflow: hidden !important;
|
||||
clip: rect(0, 0, 0, 0) !important;
|
||||
white-space: nowrap !important;
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
padding: 0.75rem 1.5rem !important;
|
||||
margin: 0 !important;
|
||||
overflow: visible !important;
|
||||
clip: auto !important;
|
||||
white-space: normal !important;
|
||||
background: var(--witch-rose) !important;
|
||||
color: var(--witch-moon) !important;
|
||||
text-decoration: none !important;
|
||||
font-weight: 600 !important;
|
||||
border-radius: 0 0 4px 0 !important;
|
||||
z-index: 10000 !important;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
min-height: calc(100vh - 60px); // Assuming header is ~60px
|
||||
background-color: transparent; // Let the body background show through
|
||||
|
||||
@@ -18,4 +18,13 @@ export class App implements OnInit {
|
||||
ngOnInit(): void {
|
||||
this.analytics.initialise();
|
||||
}
|
||||
|
||||
skipToMainContent(event: Event): void {
|
||||
event.preventDefault();
|
||||
const mainContent = document.getElementById('main-content');
|
||||
if (mainContent) {
|
||||
mainContent.focus();
|
||||
mainContent.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,23 +143,36 @@ button {
|
||||
background: var(--witch-plum);
|
||||
}
|
||||
|
||||
// Skip to main content link
|
||||
// Skip to main content link - visually hidden but accessible to screen readers
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 0.75rem 1.5rem;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
clip: auto;
|
||||
white-space: normal;
|
||||
background: var(--witch-rose);
|
||||
color: var(--witch-moon);
|
||||
padding: 0.75rem 1.5rem;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
border-radius: 0 0 4px 0;
|
||||
z-index: 10000;
|
||||
transition: top 0.3s;
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
// Enhanced focus styles for keyboard navigation
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/naming-convention -- Achievement keys use snake_case for database identifiers */
|
||||
/* eslint-disable sort-keys -- Achievements organized by category, not alphabetically */
|
||||
/* eslint-disable sort-keys-fix/sort-keys-fix -- Achievements organized by category, not alphabetically */
|
||||
/* eslint-disable stylistic/key-spacing -- Aligned for readability */
|
||||
/* eslint-disable import/group-exports -- Single export at end of file */
|
||||
import {
|
||||
AchievementCategory,
|
||||
type AchievementDefinition,
|
||||
@@ -11,6 +16,61 @@ import {
|
||||
|
||||
export const ACHIEVEMENTS: Record<string, AchievementDefinition> = {
|
||||
|
||||
like_enthusiast: {
|
||||
category: AchievementCategory.Like,
|
||||
description: "Like 25 items in the library",
|
||||
icon: "💕",
|
||||
key: "like_enthusiast",
|
||||
points: 50,
|
||||
title: "Enthusiast",
|
||||
requirements: { count: 25 },
|
||||
tier: AchievementTier.Bronze,
|
||||
},
|
||||
|
||||
like_fan: {
|
||||
category: AchievementCategory.Like,
|
||||
description: "Like 100 items in the library",
|
||||
icon: "💖",
|
||||
key: "like_fan",
|
||||
points: 100,
|
||||
title: "Fan",
|
||||
requirements: { count: 100 },
|
||||
tier: AchievementTier.Silver,
|
||||
},
|
||||
|
||||
suggestion_acceptance_100: {
|
||||
category: AchievementCategory.Suggestion,
|
||||
description: "Achieve 100% acceptance rate (minimum 10 suggestions)",
|
||||
icon: "✨",
|
||||
key: "suggestion_acceptance_100",
|
||||
points: 500,
|
||||
requirements: { count: 10, rate: 1 },
|
||||
tier: AchievementTier.Platinum,
|
||||
title: "Perfect Record",
|
||||
},
|
||||
|
||||
like_book_lover: {
|
||||
description: "Like 50 books",
|
||||
key: "like_book_lover",
|
||||
category: AchievementCategory.Like,
|
||||
title: "Book Lover",
|
||||
icon: "📚",
|
||||
points: 100,
|
||||
tier: AchievementTier.Silver,
|
||||
requirements: { count: 50 },
|
||||
},
|
||||
|
||||
suggestion_acceptance_75: {
|
||||
category: AchievementCategory.Suggestion,
|
||||
description: "Maintain a 75%+ acceptance rate (minimum 20 suggestions)",
|
||||
icon: "🎯",
|
||||
key: "suggestion_acceptance_75",
|
||||
points: 300,
|
||||
requirements: { count: 20, rate: 0.75 },
|
||||
title: "Quality Over Quantity",
|
||||
tier: AchievementTier.Gold,
|
||||
},
|
||||
|
||||
suggestion_approved: {
|
||||
category: AchievementCategory.Suggestion,
|
||||
description: "Have your first suggestion accepted",
|
||||
@@ -18,8 +78,19 @@ export const ACHIEVEMENTS: Record<string, AchievementDefinition> = {
|
||||
key: "suggestion_approved",
|
||||
points: 50,
|
||||
requirements: { count: 1 },
|
||||
title: "Approved!",
|
||||
tier: AchievementTier.Bronze,
|
||||
title: "Approved!",
|
||||
},
|
||||
|
||||
like_cinephile: {
|
||||
description: "Like 50 shows/films",
|
||||
category: AchievementCategory.Like,
|
||||
key: "like_cinephile",
|
||||
icon: "🎬",
|
||||
title: "Cinephile",
|
||||
points: 100,
|
||||
requirements: { count: 50 },
|
||||
tier: AchievementTier.Silver,
|
||||
},
|
||||
|
||||
suggestion_contributor: {
|
||||
@@ -33,6 +104,17 @@ export const ACHIEVEMENTS: Record<string, AchievementDefinition> = {
|
||||
title: "Contributor",
|
||||
},
|
||||
|
||||
like_diverse: {
|
||||
description: "Like items from all 6 media types",
|
||||
category: AchievementCategory.Like,
|
||||
key: "like_diverse",
|
||||
icon: "🌍",
|
||||
title: "Diverse Taste",
|
||||
points: 200,
|
||||
requirements: { diversity: true },
|
||||
tier: AchievementTier.Gold,
|
||||
},
|
||||
|
||||
suggestion_dedicated: {
|
||||
category: AchievementCategory.Suggestion,
|
||||
description: "Submit 100 suggestions to the library",
|
||||
@@ -44,18 +126,42 @@ export const ACHIEVEMENTS: Record<string, AchievementDefinition> = {
|
||||
title: "Dedicated",
|
||||
},
|
||||
|
||||
suggestion_acceptance_100: {
|
||||
key: "suggestion_acceptance_100",
|
||||
description: "Achieve 100% acceptance rate (minimum 10 suggestions)",
|
||||
title: "Perfect Record",
|
||||
category: AchievementCategory.Suggestion,
|
||||
icon: "✨",
|
||||
tier: AchievementTier.Platinum,
|
||||
points: 500,
|
||||
requirements: { count: 10, rate: 1.0 },
|
||||
// ========== COMMENT ACHIEVEMENTS (12) ==========
|
||||
comment_first: {
|
||||
description: "Leave your first comment in the library",
|
||||
category: AchievementCategory.Comment,
|
||||
key: "comment_first",
|
||||
icon: "💬",
|
||||
title: "First Thoughts",
|
||||
points: 25,
|
||||
requirements: { count: 1 },
|
||||
tier: AchievementTier.Bronze,
|
||||
},
|
||||
|
||||
suggestion_enthusiast: {
|
||||
category: AchievementCategory.Suggestion,
|
||||
description: "Submit 5 suggestions in one day",
|
||||
icon: "🔥",
|
||||
key: "suggestion_enthusiast",
|
||||
points: 150,
|
||||
requirements: { count: 5, dayRange: 1 },
|
||||
title: "Suggestion Enthusiast",
|
||||
tier: AchievementTier.Silver,
|
||||
},
|
||||
|
||||
comment_critic: {
|
||||
description: "Leave 50 comments in the library",
|
||||
category: AchievementCategory.Comment,
|
||||
key: "comment_critic",
|
||||
icon: "🗣️",
|
||||
title: "Critic",
|
||||
points: 100,
|
||||
requirements: { count: 50 },
|
||||
tier: AchievementTier.Silver,
|
||||
},
|
||||
|
||||
// ========== SUGGESTION ACHIEVEMENTS (15) ==========
|
||||
suggestion_first_steps: {
|
||||
suggestion_first_steps: {
|
||||
category: AchievementCategory.Suggestion,
|
||||
description: "Submit your first 10 suggestions to the library",
|
||||
icon: "🌱",
|
||||
@@ -65,16 +171,18 @@ suggestion_first_steps: {
|
||||
tier: AchievementTier.Bronze,
|
||||
title: "First Steps",
|
||||
},
|
||||
suggestion_acceptance_75: {
|
||||
description: "Maintain a 75%+ acceptance rate (minimum 20 suggestions)",
|
||||
key: "suggestion_acceptance_75",
|
||||
category: AchievementCategory.Suggestion,
|
||||
title: "Quality Over Quantity",
|
||||
icon: "🎯",
|
||||
points: 300,
|
||||
|
||||
comment_expert: {
|
||||
category: AchievementCategory.Comment,
|
||||
description: "Leave 100 comments in the library",
|
||||
icon: "🎭",
|
||||
key: "comment_expert",
|
||||
points: 250,
|
||||
title: "Expert Critic",
|
||||
requirements: { count: 100 },
|
||||
tier: AchievementTier.Gold,
|
||||
requirements: { count: 20, rate: 0.75 },
|
||||
},
|
||||
|
||||
suggestion_legend: {
|
||||
category: AchievementCategory.Suggestion,
|
||||
description: "Submit 500 suggestions to the library",
|
||||
@@ -85,16 +193,18 @@ suggestion_first_steps: {
|
||||
tier: AchievementTier.Diamond,
|
||||
title: "Legend",
|
||||
},
|
||||
like_enthusiast: {
|
||||
key: "like_enthusiast",
|
||||
description: "Like 25 items in the library",
|
||||
title: "Enthusiast",
|
||||
category: AchievementCategory.Like,
|
||||
icon: "💕",
|
||||
tier: AchievementTier.Bronze,
|
||||
points: 50,
|
||||
requirements: { count: 25 },
|
||||
|
||||
comment_legend: {
|
||||
category: AchievementCategory.Comment,
|
||||
description: "Leave 500 comments in the library",
|
||||
icon: "🏅",
|
||||
key: "comment_legend",
|
||||
points: 1000,
|
||||
title: "Review Legend",
|
||||
requirements: { count: 500 },
|
||||
tier: AchievementTier.Diamond,
|
||||
},
|
||||
|
||||
suggestion_master: {
|
||||
category: AchievementCategory.Suggestion,
|
||||
description: "Submit 250 suggestions to the library",
|
||||
@@ -102,19 +212,21 @@ suggestion_first_steps: {
|
||||
key: "suggestion_master",
|
||||
points: 500,
|
||||
requirements: { count: 250 },
|
||||
title: "Master Curator",
|
||||
tier: AchievementTier.Platinum,
|
||||
title: "Master Curator",
|
||||
},
|
||||
like_fan: {
|
||||
description: "Like 100 items in the library",
|
||||
key: "like_fan",
|
||||
category: AchievementCategory.Like,
|
||||
title: "Fan",
|
||||
icon: "💖",
|
||||
points: 100,
|
||||
|
||||
comment_detailed: {
|
||||
category: AchievementCategory.Comment,
|
||||
description: "Write 10 comments with 500+ characters",
|
||||
icon: "📝",
|
||||
key: "comment_detailed",
|
||||
points: 150,
|
||||
requirements: { count: 10 },
|
||||
title: "Detailed Critic",
|
||||
tier: AchievementTier.Silver,
|
||||
requirements: { count: 100 },
|
||||
},
|
||||
|
||||
suggestion_quality_10: {
|
||||
category: AchievementCategory.Suggestion,
|
||||
description: "Have 10 suggestions accepted",
|
||||
@@ -122,59 +234,74 @@ suggestion_first_steps: {
|
||||
key: "suggestion_quality_10",
|
||||
points: 200,
|
||||
requirements: { count: 10 },
|
||||
title: "Elite Curator",
|
||||
tier: AchievementTier.Gold,
|
||||
},
|
||||
suggestion_enthusiast: {
|
||||
description: "Submit 5 suggestions in one day",
|
||||
category: AchievementCategory.Suggestion,
|
||||
key: "suggestion_enthusiast",
|
||||
icon: "🔥",
|
||||
title: "Suggestion Enthusiast",
|
||||
points: 150,
|
||||
requirements: { count: 5, dayRange: 1 },
|
||||
tier: AchievementTier.Silver,
|
||||
},
|
||||
suggestion_quality_100: {
|
||||
description: "Have 100 suggestions accepted",
|
||||
key: "suggestion_quality_100",
|
||||
category: AchievementCategory.Suggestion,
|
||||
title: "Ultimate Curator",
|
||||
icon: "👸",
|
||||
points: 1500,
|
||||
tier: AchievementTier.Diamond,
|
||||
requirements: { count: 100 },
|
||||
},
|
||||
like_book_lover: {
|
||||
description: "Like 50 books",
|
||||
key: "like_book_lover",
|
||||
category: AchievementCategory.Like,
|
||||
title: "Book Lover",
|
||||
icon: "📚",
|
||||
points: 100,
|
||||
tier: AchievementTier.Silver,
|
||||
requirements: { count: 50 },
|
||||
},
|
||||
suggestion_quality_25: {
|
||||
description: "Have 25 suggestions accepted",
|
||||
key: "suggestion_quality_25",
|
||||
category: AchievementCategory.Suggestion,
|
||||
title: "Master Curator",
|
||||
icon: "💫",
|
||||
points: 400,
|
||||
tier: AchievementTier.Platinum,
|
||||
requirements: { count: 25 },
|
||||
title: "Elite Curator",
|
||||
},
|
||||
|
||||
like_cinephile: {
|
||||
description: "Like 50 shows/films",
|
||||
key: "like_cinephile",
|
||||
category: AchievementCategory.Like,
|
||||
title: "Cinephile",
|
||||
icon: "🎬",
|
||||
points: 100,
|
||||
comment_essay: {
|
||||
category: AchievementCategory.Comment,
|
||||
description: "Write 5 comments with 1000+ characters",
|
||||
icon: "📄",
|
||||
key: "comment_essay",
|
||||
points: 300,
|
||||
requirements: { count: 5 },
|
||||
tier: AchievementTier.Gold,
|
||||
title: "Essay Writer",
|
||||
},
|
||||
|
||||
suggestion_quality_100: {
|
||||
description: "Have 100 suggestions accepted",
|
||||
category: AchievementCategory.Suggestion,
|
||||
key: "suggestion_quality_100",
|
||||
icon: "👸",
|
||||
title: "Ultimate Curator",
|
||||
points: 1500,
|
||||
requirements: { count: 100 },
|
||||
tier: AchievementTier.Diamond,
|
||||
},
|
||||
|
||||
comment_diverse: {
|
||||
category: AchievementCategory.Comment,
|
||||
description: "Comment on all 6 media types",
|
||||
icon: "🎨",
|
||||
key: "comment_diverse",
|
||||
points: 250,
|
||||
requirements: { diversity: true },
|
||||
title: "Well-Rounded Critic",
|
||||
tier: AchievementTier.Gold,
|
||||
},
|
||||
|
||||
suggestion_quality_25: {
|
||||
description: "Have 25 suggestions accepted",
|
||||
category: AchievementCategory.Suggestion,
|
||||
key: "suggestion_quality_25",
|
||||
icon: "💫",
|
||||
title: "Master Curator",
|
||||
points: 400,
|
||||
requirements: { count: 25 },
|
||||
tier: AchievementTier.Platinum,
|
||||
},
|
||||
|
||||
comment_first_to_comment: {
|
||||
category: AchievementCategory.Comment,
|
||||
description: "Be the first to comment on 10 items",
|
||||
icon: "🗨️",
|
||||
key: "comment_first_to_comment",
|
||||
points: 200,
|
||||
requirements: { count: 10 },
|
||||
tier: AchievementTier.Silver,
|
||||
requirements: { count: 50 },
|
||||
title: "Discussion Starter",
|
||||
},
|
||||
|
||||
comment_master: {
|
||||
category: AchievementCategory.Comment,
|
||||
description: "Leave 250 comments in the library",
|
||||
icon: "📖",
|
||||
key: "comment_master",
|
||||
points: 500,
|
||||
title: "Master Reviewer",
|
||||
requirements: { count: 250 },
|
||||
tier: AchievementTier.Platinum,
|
||||
},
|
||||
|
||||
suggestion_quality_5: {
|
||||
@@ -188,201 +315,6 @@ suggestion_first_steps: {
|
||||
requirements: { count: 5 },
|
||||
},
|
||||
|
||||
like_diverse: {
|
||||
description: "Like items from all 6 media types",
|
||||
key: "like_diverse",
|
||||
category: AchievementCategory.Like,
|
||||
title: "Diverse Taste",
|
||||
icon: "🌍",
|
||||
points: 200,
|
||||
tier: AchievementTier.Gold,
|
||||
requirements: { diversity: true },
|
||||
},
|
||||
|
||||
// ========== COMMENT ACHIEVEMENTS (12) ==========
|
||||
comment_first: {
|
||||
key: "comment_first",
|
||||
description: "Leave your first comment in the library",
|
||||
title: "First Thoughts",
|
||||
category: AchievementCategory.Comment,
|
||||
icon: "💬",
|
||||
tier: AchievementTier.Bronze,
|
||||
points: 25,
|
||||
requirements: { count: 1 },
|
||||
},
|
||||
|
||||
|
||||
suggestion_quality_50: {
|
||||
key: "suggestion_quality_50",
|
||||
title: "Grand Master",
|
||||
description: "Have 50 suggestions accepted",
|
||||
category: AchievementCategory.Suggestion,
|
||||
tier: AchievementTier.Platinum,
|
||||
icon: "🎖️",
|
||||
points: 700,
|
||||
requirements: { count: 50 },
|
||||
},
|
||||
|
||||
comment_critic: {
|
||||
key: "comment_critic",
|
||||
description: "Leave 50 comments in the library",
|
||||
title: "Critic",
|
||||
category: AchievementCategory.Comment,
|
||||
icon: "🗣️",
|
||||
tier: AchievementTier.Silver,
|
||||
points: 100,
|
||||
requirements: { count: 50 },
|
||||
},
|
||||
|
||||
|
||||
suggestion_renaissance: {
|
||||
description: "Submit accepted suggestions across all 6 media types",
|
||||
key: "suggestion_renaissance",
|
||||
category: AchievementCategory.Suggestion,
|
||||
title: "Renaissance Person",
|
||||
icon: "🌈",
|
||||
points: 400,
|
||||
tier: AchievementTier.Gold,
|
||||
requirements: { diversity: true },
|
||||
},
|
||||
|
||||
|
||||
comment_expert: {
|
||||
description: "Leave 100 comments in the library",
|
||||
key: "comment_expert",
|
||||
category: AchievementCategory.Comment,
|
||||
title: "Expert Critic",
|
||||
icon: "🎭",
|
||||
points: 250,
|
||||
tier: AchievementTier.Gold,
|
||||
requirements: { count: 100 },
|
||||
},
|
||||
|
||||
// ========== LIKE ACHIEVEMENTS (12) ==========
|
||||
like_first: {
|
||||
description: "Like your first item in the library",
|
||||
key: "like_first",
|
||||
category: AchievementCategory.Like,
|
||||
title: "First Like",
|
||||
icon: "❤️",
|
||||
points: 25,
|
||||
tier: AchievementTier.Bronze,
|
||||
requirements: { count: 1 },
|
||||
},
|
||||
|
||||
comment_legend: {
|
||||
description: "Leave 500 comments in the library",
|
||||
key: "comment_legend",
|
||||
category: AchievementCategory.Comment,
|
||||
title: "Review Legend",
|
||||
icon: "🏅",
|
||||
points: 1000,
|
||||
tier: AchievementTier.Diamond,
|
||||
requirements: { count: 500 },
|
||||
},
|
||||
|
||||
like_mega_fan: {
|
||||
description: "Like 500 items in the library",
|
||||
key: "like_mega_fan",
|
||||
category: AchievementCategory.Like,
|
||||
title: "Mega Fan",
|
||||
icon: "💗",
|
||||
points: 500,
|
||||
tier: AchievementTier.Platinum,
|
||||
requirements: { count: 500 },
|
||||
},
|
||||
|
||||
comment_detailed: {
|
||||
description: "Write 10 comments with 500+ characters",
|
||||
category: AchievementCategory.Comment,
|
||||
key: "comment_detailed",
|
||||
icon: "📝",
|
||||
title: "Detailed Critic",
|
||||
points: 150,
|
||||
requirements: { count: 10 },
|
||||
tier: AchievementTier.Silver,
|
||||
},
|
||||
|
||||
like_super_fan: {
|
||||
key: "like_super_fan",
|
||||
description: "Like 250 items in the library",
|
||||
title: "Super Fan",
|
||||
category: AchievementCategory.Like,
|
||||
icon: "💝",
|
||||
tier: AchievementTier.Gold,
|
||||
points: 250,
|
||||
requirements: { count: 250 },
|
||||
},
|
||||
comment_essay: {
|
||||
category: AchievementCategory.Comment,
|
||||
description: "Write 5 comments with 1000+ characters",
|
||||
icon: "📄",
|
||||
key: "comment_essay",
|
||||
points: 300,
|
||||
title: "Essay Writer",
|
||||
requirements: { count: 5 },
|
||||
tier: AchievementTier.Gold,
|
||||
},
|
||||
like_legendary: {
|
||||
key: "like_legendary",
|
||||
description: "Like 1000 items in the library",
|
||||
title: "Legendary Fan",
|
||||
category: AchievementCategory.Like,
|
||||
icon: "💞",
|
||||
tier: AchievementTier.Diamond,
|
||||
points: 1000,
|
||||
requirements: { count: 1000 },
|
||||
},
|
||||
comment_diverse: {
|
||||
description: "Comment on all 6 media types",
|
||||
category: AchievementCategory.Comment,
|
||||
key: "comment_diverse",
|
||||
icon: "🎨",
|
||||
title: "Well-Rounded Critic",
|
||||
points: 250,
|
||||
requirements: { diversity: true },
|
||||
tier: AchievementTier.Gold,
|
||||
},
|
||||
like_gamer: {
|
||||
key: "like_gamer",
|
||||
description: "Like 50 games",
|
||||
title: "Gamer",
|
||||
category: AchievementCategory.Like,
|
||||
icon: "🎮",
|
||||
tier: AchievementTier.Silver,
|
||||
points: 100,
|
||||
requirements: { count: 50 },
|
||||
},
|
||||
comment_first_to_comment: {
|
||||
category: AchievementCategory.Comment,
|
||||
description: "Be the first to comment on 10 items",
|
||||
icon: "🗨️",
|
||||
key: "comment_first_to_comment",
|
||||
points: 200,
|
||||
title: "Discussion Starter",
|
||||
requirements: { count: 10 },
|
||||
tier: AchievementTier.Silver,
|
||||
},
|
||||
like_music: {
|
||||
key: "like_music",
|
||||
description: "Like 50 music albums",
|
||||
title: "Music Enthusiast",
|
||||
category: AchievementCategory.Like,
|
||||
icon: "🎵",
|
||||
tier: AchievementTier.Silver,
|
||||
points: 100,
|
||||
requirements: { count: 50 },
|
||||
},
|
||||
comment_master: {
|
||||
description: "Leave 250 comments in the library",
|
||||
category: AchievementCategory.Comment,
|
||||
key: "comment_master",
|
||||
icon: "📖",
|
||||
title: "Master Reviewer",
|
||||
points: 500,
|
||||
requirements: { count: 250 },
|
||||
tier: AchievementTier.Platinum,
|
||||
},
|
||||
comment_novel: {
|
||||
category: AchievementCategory.Comment,
|
||||
description: "Write 3 comments with 2000+ characters",
|
||||
@@ -393,26 +325,40 @@ like_first: {
|
||||
tier: AchievementTier.Platinum,
|
||||
title: "Novel Writer",
|
||||
},
|
||||
like_binge: {
|
||||
key: "like_binge",
|
||||
title: "Binge Liker",
|
||||
description: "Like 20+ items in one day",
|
||||
category: AchievementCategory.Like,
|
||||
tier: AchievementTier.Silver,
|
||||
icon: "⚡",
|
||||
points: 150,
|
||||
requirements: { count: 20, dayRange: 1 },
|
||||
|
||||
suggestion_quality_50: {
|
||||
key: "suggestion_quality_50",
|
||||
description: "Have 50 suggestions accepted",
|
||||
title: "Grand Master",
|
||||
category: AchievementCategory.Suggestion,
|
||||
icon: "🎖️",
|
||||
tier: AchievementTier.Platinum,
|
||||
points: 700,
|
||||
requirements: { count: 50 },
|
||||
},
|
||||
|
||||
comment_reviewer: {
|
||||
key: "comment_reviewer",
|
||||
description: "Leave 10 comments in the library",
|
||||
title: "Reviewer",
|
||||
category: AchievementCategory.Comment,
|
||||
key: "comment_reviewer",
|
||||
icon: "✍️",
|
||||
tier: AchievementTier.Bronze,
|
||||
title: "Reviewer",
|
||||
points: 50,
|
||||
requirements: { count: 10 },
|
||||
tier: AchievementTier.Bronze,
|
||||
},
|
||||
|
||||
suggestion_renaissance: {
|
||||
description: "Submit accepted suggestions across all 6 media types",
|
||||
category: AchievementCategory.Suggestion,
|
||||
key: "suggestion_renaissance",
|
||||
icon: "🌈",
|
||||
title: "Renaissance Person",
|
||||
points: 400,
|
||||
requirements: { diversity: true },
|
||||
tier: AchievementTier.Gold,
|
||||
},
|
||||
|
||||
comment_thoughtful: {
|
||||
category: AchievementCategory.Comment,
|
||||
description: "Comment on 50 different items",
|
||||
@@ -425,36 +371,86 @@ like_first: {
|
||||
},
|
||||
|
||||
engagement_early_adopter: {
|
||||
description: "Be among the first 10 users to join",
|
||||
category: AchievementCategory.Engagement,
|
||||
key: "engagement_early_adopter",
|
||||
description: "Be among the first 10 users to join",
|
||||
icon: "🌟",
|
||||
title: "Early Adopter",
|
||||
key: "engagement_early_adopter",
|
||||
points: 1000,
|
||||
requirements: {},
|
||||
tier: AchievementTier.Diamond,
|
||||
title: "Early Adopter",
|
||||
},
|
||||
|
||||
engagement_power_user: {
|
||||
description: "Achieve Triple Threat 10 times",
|
||||
category: AchievementCategory.Engagement,
|
||||
key: "engagement_power_user",
|
||||
icon: "💫",
|
||||
title: "Power User",
|
||||
points: 750,
|
||||
requirements: { count: 10 },
|
||||
tier: AchievementTier.Platinum,
|
||||
// ========== LIKE ACHIEVEMENTS (12) ==========
|
||||
like_first: {
|
||||
description: "Like your first item in the library",
|
||||
key: "like_first",
|
||||
category: AchievementCategory.Like,
|
||||
title: "First Like",
|
||||
icon: "❤️",
|
||||
points: 25,
|
||||
tier: AchievementTier.Bronze,
|
||||
requirements: { count: 1 },
|
||||
},
|
||||
|
||||
engagement_founding_100: {
|
||||
category: AchievementCategory.Engagement,
|
||||
description: "Be among the first 100 users to join",
|
||||
icon: "🎖️",
|
||||
key: "engagement_founding_100",
|
||||
points: 500,
|
||||
title: "Founding Member",
|
||||
requirements: {},
|
||||
tier: AchievementTier.Gold,
|
||||
title: "Founding Member",
|
||||
},
|
||||
like_mega_fan: {
|
||||
description: "Like 500 items in the library",
|
||||
key: "like_mega_fan",
|
||||
category: AchievementCategory.Like,
|
||||
title: "Mega Fan",
|
||||
icon: "💗",
|
||||
points: 500,
|
||||
tier: AchievementTier.Platinum,
|
||||
requirements: { count: 500 },
|
||||
},
|
||||
engagement_founding_1000: {
|
||||
category: AchievementCategory.Engagement,
|
||||
description: "Be among the first 1000 users to join",
|
||||
icon: "🚀",
|
||||
key: "engagement_founding_1000",
|
||||
points: 200,
|
||||
requirements: {},
|
||||
tier: AchievementTier.Silver,
|
||||
title: "Pioneer",
|
||||
},
|
||||
like_super_fan: {
|
||||
key: "like_super_fan",
|
||||
description: "Like 250 items in the library",
|
||||
title: "Super Fan",
|
||||
category: AchievementCategory.Like,
|
||||
icon: "💝",
|
||||
tier: AchievementTier.Gold,
|
||||
points: 250,
|
||||
requirements: { count: 250 },
|
||||
},
|
||||
engagement_power_user: {
|
||||
category: AchievementCategory.Engagement,
|
||||
description: "Achieve Triple Threat 10 times",
|
||||
icon: "💫",
|
||||
key: "engagement_power_user",
|
||||
points: 750,
|
||||
requirements: { count: 10 },
|
||||
tier: AchievementTier.Platinum,
|
||||
title: "Power User",
|
||||
},
|
||||
|
||||
like_legendary: {
|
||||
key: "like_legendary",
|
||||
description: "Like 1000 items in the library",
|
||||
title: "Legendary Fan",
|
||||
category: AchievementCategory.Like,
|
||||
icon: "💞",
|
||||
tier: AchievementTier.Diamond,
|
||||
points: 1000,
|
||||
requirements: { count: 1000 },
|
||||
},
|
||||
|
||||
engagement_streak_100: {
|
||||
@@ -463,31 +459,42 @@ like_first: {
|
||||
icon: "🏆",
|
||||
key: "engagement_streak_100",
|
||||
points: 500,
|
||||
title: "Committed",
|
||||
requirements: { streak: 100 },
|
||||
tier: AchievementTier.Gold,
|
||||
title: "Committed",
|
||||
},
|
||||
|
||||
engagement_founding_1000: {
|
||||
category: AchievementCategory.Engagement,
|
||||
description: "Be among the first 1000 users to join",
|
||||
icon: "🚀",
|
||||
key: "engagement_founding_1000",
|
||||
points: 200,
|
||||
requirements: {},
|
||||
title: "Pioneer",
|
||||
like_gamer: {
|
||||
key: "like_gamer",
|
||||
description: "Like 50 games",
|
||||
title: "Gamer",
|
||||
category: AchievementCategory.Like,
|
||||
icon: "🎮",
|
||||
tier: AchievementTier.Silver,
|
||||
points: 100,
|
||||
requirements: { count: 50 },
|
||||
},
|
||||
|
||||
engagement_streak_30: {
|
||||
description: "Login for 30 days in a row",
|
||||
category: AchievementCategory.Engagement,
|
||||
key: "engagement_streak_30",
|
||||
description: "Login for 30 days in a row",
|
||||
icon: "💪",
|
||||
title: "Dedicated",
|
||||
key: "engagement_streak_30",
|
||||
points: 250,
|
||||
requirements: { streak: 30 },
|
||||
tier: AchievementTier.Silver,
|
||||
title: "Dedicated",
|
||||
},
|
||||
|
||||
like_music: {
|
||||
key: "like_music",
|
||||
description: "Like 50 music albums",
|
||||
title: "Music Enthusiast",
|
||||
category: AchievementCategory.Like,
|
||||
icon: "🎵",
|
||||
tier: AchievementTier.Silver,
|
||||
points: 100,
|
||||
requirements: { count: 50 },
|
||||
},
|
||||
|
||||
engagement_streak_365: {
|
||||
@@ -496,20 +503,31 @@ like_first: {
|
||||
icon: "⚡",
|
||||
key: "engagement_streak_365",
|
||||
points: 2000,
|
||||
title: "Unstoppable",
|
||||
requirements: { streak: 365 },
|
||||
tier: AchievementTier.Diamond,
|
||||
title: "Unstoppable",
|
||||
},
|
||||
|
||||
engagement_streak_7: {
|
||||
description: "Login for 7 days in a row",
|
||||
key: "engagement_streak_7",
|
||||
category: AchievementCategory.Engagement,
|
||||
title: "Daily Visitor",
|
||||
description: "Login for 7 days in a row",
|
||||
icon: "🔥",
|
||||
key: "engagement_streak_7",
|
||||
points: 100,
|
||||
tier: AchievementTier.Bronze,
|
||||
requirements: { streak: 7 },
|
||||
tier: AchievementTier.Bronze,
|
||||
title: "Daily Visitor",
|
||||
},
|
||||
|
||||
like_binge: {
|
||||
key: "like_binge",
|
||||
title: "Binge Liker",
|
||||
description: "Like 20+ items in one day",
|
||||
category: AchievementCategory.Like,
|
||||
tier: AchievementTier.Silver,
|
||||
icon: "⚡",
|
||||
points: 150,
|
||||
requirements: { count: 20, dayRange: 1 },
|
||||
},
|
||||
|
||||
engagement_triple_threat: {
|
||||
@@ -519,11 +537,11 @@ like_first: {
|
||||
key: "engagement_triple_threat",
|
||||
points: 200,
|
||||
requirements: { dayRange: 1 },
|
||||
title: "Triple Threat",
|
||||
tier: AchievementTier.Silver,
|
||||
title: "Triple Threat",
|
||||
},
|
||||
|
||||
engagement_veteran_180: {
|
||||
engagement_veteran_180: {
|
||||
category: AchievementCategory.Engagement,
|
||||
description: "Have an account for 6 months",
|
||||
icon: "📅",
|
||||
@@ -533,17 +551,7 @@ engagement_veteran_180: {
|
||||
tier: AchievementTier.Silver,
|
||||
title: "Seasoned",
|
||||
},
|
||||
// ========== ENGAGEMENT ACHIEVEMENTS (15) ==========
|
||||
engagement_welcome: {
|
||||
key: "engagement_welcome",
|
||||
title: "Welcome!",
|
||||
description: "Complete your profile setup",
|
||||
category: AchievementCategory.Engagement,
|
||||
tier: AchievementTier.Bronze,
|
||||
icon: "👋",
|
||||
points: 25,
|
||||
requirements: {},
|
||||
},
|
||||
|
||||
engagement_veteran_1825: {
|
||||
category: AchievementCategory.Engagement,
|
||||
description: "Have an account for 5 years",
|
||||
@@ -554,6 +562,7 @@ engagement_welcome: {
|
||||
tier: AchievementTier.Diamond,
|
||||
title: "Legend",
|
||||
},
|
||||
|
||||
engagement_veteran_30: {
|
||||
category: AchievementCategory.Engagement,
|
||||
description: "Have an account for 30 days",
|
||||
@@ -564,6 +573,7 @@ engagement_welcome: {
|
||||
tier: AchievementTier.Bronze,
|
||||
title: "Veteran",
|
||||
},
|
||||
|
||||
engagement_veteran_365: {
|
||||
category: AchievementCategory.Engagement,
|
||||
description: "Have an account for 1 year",
|
||||
@@ -574,6 +584,7 @@ engagement_welcome: {
|
||||
tier: AchievementTier.Gold,
|
||||
title: "Longstanding",
|
||||
},
|
||||
|
||||
engagement_veteran_730: {
|
||||
category: AchievementCategory.Engagement,
|
||||
description: "Have an account for 2 years",
|
||||
@@ -584,6 +595,17 @@ engagement_welcome: {
|
||||
tier: AchievementTier.Platinum,
|
||||
title: "Elder",
|
||||
},
|
||||
// ========== ENGAGEMENT ACHIEVEMENTS (15) ==========
|
||||
engagement_welcome: {
|
||||
category: AchievementCategory.Engagement,
|
||||
description: "Complete your profile setup",
|
||||
icon: "👋",
|
||||
key: "engagement_welcome",
|
||||
points: 25,
|
||||
requirements: {},
|
||||
tier: AchievementTier.Bronze,
|
||||
title: "Welcome!",
|
||||
},
|
||||
|
||||
report_accuracy_80: {
|
||||
category: AchievementCategory.Report,
|
||||
@@ -663,14 +685,14 @@ engagement_welcome: {
|
||||
},
|
||||
// ========== REPORT ACHIEVEMENTS (8) ==========
|
||||
report_watchful: {
|
||||
description: "Submit your first valid report (ACTION_TAKEN)",
|
||||
category: AchievementCategory.Report,
|
||||
key: "report_watchful",
|
||||
description: "Submit your first valid report (ACTION_TAKEN)",
|
||||
icon: "👀",
|
||||
title: "Watchful Eye",
|
||||
key: "report_watchful",
|
||||
points: 50,
|
||||
requirements: { count: 1 },
|
||||
tier: AchievementTier.Bronze,
|
||||
title: "Watchful Eye",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/naming-convention -- Enum members use PascalCase for display values */
|
||||
/* eslint-disable import/group-exports -- Multiple exports for better organization */
|
||||
|
||||
export enum AchievementCategory {
|
||||
Suggestion = "SUGGESTION",
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
/**
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/naming-convention -- Enum members use UPPER_CASE for database values */
|
||||
/* eslint-disable import/group-exports -- Multiple exports for better organization */
|
||||
|
||||
export enum ReportReason {
|
||||
INAPPROPRIATE_CONTENT = "INAPPROPRIATE_CONTENT",
|
||||
HARASSMENT = "HARASSMENT",
|
||||
|
||||
Reference in New Issue
Block a user