From e380c0a31c39fd26ad83b23fca45a8e9d6998ee3 Mon Sep 17 00:00:00 2001 From: Jamie Brunton Date: Tue, 27 Jan 2026 22:50:46 +0000 Subject: [PATCH] feat(admin): add NSFW flag to packs, remove intensity Re-add is_explicit toggle on pack and dare pack forms labeled as "NSFW". Show NSFW badge on pack cards and user enabled packs. Remove all intensity/comfort level references from admin UI including form fields, analytics charts, review dialog columns, AI generation prompts/scoring, cherry-pick balancing, and settings toggles. The database columns remain unchanged. --- .../src/components/ai/AIPolishButton.tsx | 5 +- .../src/components/ai/AiGeneratorDialog.tsx | 2 - .../src/components/ai/GenerationForm.tsx | 28 -- .../components/ai/hooks/useAiGeneration.ts | 41 +-- .../src/components/content/PackFormDialog.tsx | 4 +- .../content/ReviewQuestionsDialog.tsx | 112 +------- .../src/components/layout/Breadcrumbs.tsx | 10 +- apps/admin/src/components/ui/tabs.tsx | 2 +- apps/admin/src/lib/ai/analyzers/text.ts | 20 +- apps/admin/src/lib/ai/config.ts | 80 +----- .../src/lib/ai/generators/cherry-pick.ts | 93 +------ apps/admin/src/lib/ai/generators/council.ts | 6 +- apps/admin/src/lib/ai/generators/questions.ts | 21 +- apps/admin/src/lib/ai/index.ts | 3 - apps/admin/src/lib/ai/review.ts | 55 ++-- apps/admin/src/lib/ai/types.ts | 8 - apps/admin/src/pages/AiSettingsPage.tsx | 20 -- apps/admin/src/pages/AppSettingsPage.tsx | 16 -- apps/admin/src/pages/AuditLogsPage.tsx | 106 +++++--- apps/admin/src/pages/FeedbackPage.tsx | 82 +++--- apps/admin/src/pages/FlaggedMessagesPage.tsx | 149 +++++----- apps/admin/src/pages/RedemptionCodesPage.tsx | 242 ++++++++++------- .../src/pages/activity/UserActivityPage.tsx | 4 +- apps/admin/src/pages/admins/AdminsPage.tsx | 188 ++++++++----- .../admin/src/pages/content/DarePacksPage.tsx | 46 +--- apps/admin/src/pages/content/DaresPage.tsx | 11 +- apps/admin/src/pages/content/PacksPage.tsx | 18 +- .../pages/content/QuestionAnalyticsPage.tsx | 107 +------- .../admin/src/pages/content/QuestionsPage.tsx | 21 +- apps/admin/src/pages/users/UserDetailPage.tsx | 30 +- apps/admin/src/pages/users/UsersPage.tsx | 237 +++++++++------- new-question-types-draft.md | 256 ++++++++++++++++++ 32 files changed, 948 insertions(+), 1075 deletions(-) create mode 100644 new-question-types-draft.md diff --git a/apps/admin/src/components/ai/AIPolishButton.tsx b/apps/admin/src/components/ai/AIPolishButton.tsx index 926b502..0136b9f 100644 --- a/apps/admin/src/components/ai/AIPolishButton.tsx +++ b/apps/admin/src/components/ai/AIPolishButton.tsx @@ -10,10 +10,9 @@ interface AIPolishButtonProps { onPolished: (newText: string) => void; className?: string; disabled?: boolean; - explicit?: boolean; } -export function AIPolishButton({ text, type, onPolished, className, disabled, explicit = false }: AIPolishButtonProps) { +export function AIPolishButton({ text, type, onPolished, className, disabled }: AIPolishButtonProps) { const [loading, setLoading] = useState(false); const handlePolish = async (e: React.MouseEvent) => { @@ -25,7 +24,7 @@ export function AIPolishButton({ text, type, onPolished, className, disabled, ex setLoading(true); try { - const polished = await polishContent(text, type, explicit); + const polished = await polishContent(text, type, false); onPolished(polished); toast.success('Text polished!'); } catch (error) { diff --git a/apps/admin/src/components/ai/AiGeneratorDialog.tsx b/apps/admin/src/components/ai/AiGeneratorDialog.tsx index bc5b069..8e3d8ea 100644 --- a/apps/admin/src/components/ai/AiGeneratorDialog.tsx +++ b/apps/admin/src/components/ai/AiGeneratorDialog.tsx @@ -114,8 +114,6 @@ export function AiGeneratorDialog({ context={context} config={generation.config} onCountChange={generation.setCount} - onExplicitChange={generation.setExplicit} - onCrudeLangChange={generation.setCrudeLang} onInspirationChange={generation.setInspiration} /> diff --git a/apps/admin/src/components/ai/GenerationForm.tsx b/apps/admin/src/components/ai/GenerationForm.tsx index 3243666..5e244ba 100644 --- a/apps/admin/src/components/ai/GenerationForm.tsx +++ b/apps/admin/src/components/ai/GenerationForm.tsx @@ -1,7 +1,6 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; -import { Switch } from '@/components/ui/switch'; import type { GeneratorType, GeneratorContext, GenerationConfig } from './hooks/useAiGeneration'; // ============================================================================= @@ -13,8 +12,6 @@ interface GenerationFormProps { context?: GeneratorContext; config: GenerationConfig; onCountChange: (count: number) => void; - onExplicitChange: (isExplicit: boolean) => void; - onCrudeLangChange: (crudeLang: boolean) => void; onInspirationChange: (inspiration: string) => void; } @@ -27,8 +24,6 @@ export function GenerationForm({ context, config, onCountChange, - onExplicitChange, - onCrudeLangChange, onInspirationChange, }: GenerationFormProps) { return ( @@ -49,29 +44,6 @@ export function GenerationForm({ )} - {/* Explicit Toggle */} -
- - -
- - {/* Crude Language Override Toggle */} -
- - - - (use "fuck", "suck cock" etc. instead of tasteful phrasing) - -
- {/* Inspiration/Suggestions Textarea */}
diff --git a/apps/admin/src/components/ai/hooks/useAiGeneration.ts b/apps/admin/src/components/ai/hooks/useAiGeneration.ts index b978f7c..d178423 100644 --- a/apps/admin/src/components/ai/hooks/useAiGeneration.ts +++ b/apps/admin/src/components/ai/hooks/useAiGeneration.ts @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useCallback } from 'react'; import { generatePack, generateQuestionsWithCouncil, @@ -32,13 +32,10 @@ export interface GeneratorContext { existingCategories?: string[]; existingPacks?: string[]; existingQuestions?: string[]; - isExplicit?: boolean; } export interface GenerationConfig { count: number; - isExplicit: boolean; - crudeLang: boolean; inspiration: string; } @@ -76,8 +73,6 @@ export function useAiGeneration( // Config state const [config, setConfig] = useState({ count: 10, - isExplicit: Boolean(context?.isExplicit), - crudeLang: false, inspiration: '', }); @@ -117,27 +112,11 @@ export function useAiGeneration( // Council config const councilConfig = getCouncilConfig(); - // Update explicit flag when context changes - useEffect(() => { - setConfig(prev => ({ - ...prev, - isExplicit: Boolean(context?.isExplicit), - })); - }, [context?.isExplicit, type]); - // Config setters const setCount = useCallback((count: number) => { setConfig(prev => ({ ...prev, count })); }, []); - const setExplicit = useCallback((isExplicit: boolean) => { - setConfig(prev => ({ ...prev, isExplicit })); - }, []); - - const setCrudeLang = useCallback((crudeLang: boolean) => { - setConfig(prev => ({ ...prev, crudeLang })); - }, []); - const setInspiration = useCallback((inspiration: string) => { setConfig(prev => ({ ...prev, inspiration })); }, []); @@ -182,7 +161,7 @@ export function useAiGeneration( const reset = useCallback(() => { setResults({ pack: null, questions: [], categoryIdeas: [], packIdeas: [] }); setSelectedIndices(new Set()); - setConfig(prev => ({ ...prev, crudeLang: false, inspiration: '' })); + setConfig(prev => ({ ...prev, inspiration: '' })); setReviewState({ reviews: [], summary: null, @@ -216,7 +195,8 @@ export function useAiGeneration( setFilter('all'); setSourceModel(null); - const isExplicit = config.isExplicit; + const isExplicit = false; + const crudeLang = false; try { if (type === 'pack') { @@ -232,7 +212,7 @@ export function useAiGeneration( const result = await generatePack( context?.categoryName, isExplicit, - config.crudeLang, + crudeLang, config.inspiration || undefined, avoidNames ); @@ -249,11 +229,10 @@ export function useAiGeneration( const result = await generateQuestionsWithCouncil( context.packName, config.count, - undefined, - isExplicit ? 5 : 2, + 2, context.packDescription || undefined, context.existingQuestions, - config.crudeLang, + crudeLang, config.inspiration || undefined, setProgress ); @@ -309,7 +288,7 @@ export function useAiGeneration( const result = await suggestCategories( context?.existingCategories || [], isExplicit, - config.crudeLang, + crudeLang, config.inspiration || undefined, avoidNames ); @@ -335,7 +314,7 @@ export function useAiGeneration( context.categoryName, context?.existingPacks || [], isExplicit, - config.crudeLang, + crudeLang, config.inspiration || undefined, avoidNames ); @@ -366,8 +345,6 @@ export function useAiGeneration( // Config config, setCount, - setExplicit, - setCrudeLang, setInspiration, // Results diff --git a/apps/admin/src/components/content/PackFormDialog.tsx b/apps/admin/src/components/content/PackFormDialog.tsx index 3d82d07..c175a56 100644 --- a/apps/admin/src/components/content/PackFormDialog.tsx +++ b/apps/admin/src/components/content/PackFormDialog.tsx @@ -260,10 +260,10 @@ export function PackFormDialog({
)} - {/* Explicit Toggle */} + {/* NSFW Toggle */}
- +

Contains adult or mature content

diff --git a/apps/admin/src/components/content/ReviewQuestionsDialog.tsx b/apps/admin/src/components/content/ReviewQuestionsDialog.tsx index cb55e31..26bda2e 100644 --- a/apps/admin/src/components/content/ReviewQuestionsDialog.tsx +++ b/apps/admin/src/components/content/ReviewQuestionsDialog.tsx @@ -11,7 +11,7 @@ import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Checkbox } from '@/components/ui/checkbox'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; -import { Loader2, Sparkles, ArrowRight, Check, Wand2, ShieldCheck, Gauge, Trash2, Package } from 'lucide-react'; +import { Loader2, Sparkles, ArrowRight, Check, Wand2, ShieldCheck, Trash2, Package } from 'lucide-react'; import { analyzeQuestionText, analyzeQuestionTargets, @@ -51,7 +51,6 @@ interface ReviewQuestionsDialogProps { open: boolean; onOpenChange: (open: boolean) => void; questions: Question[]; - isExplicit?: boolean; onUpdated: () => void; } @@ -64,14 +63,13 @@ interface CombinedSuggestion { deleteSuggestion?: DeletionAnalysis; } -export function ReviewQuestionsDialog({ open, onOpenChange, questions, isExplicit = false, onUpdated }: ReviewQuestionsDialogProps) { +export function ReviewQuestionsDialog({ open, onOpenChange, questions, onUpdated }: ReviewQuestionsDialogProps) { const [analyzing, setAnalyzing] = useState(false); const [suggestions, setSuggestions] = useState([]); const [existingProps, setExistingProps] = useState([]); const [selectedTextIds, setSelectedTextIds] = useState>(new Set()); const [selectedTargetIds, setSelectedTargetIds] = useState>(new Set()); const [selectedPropsIds, setSelectedPropsIds] = useState>(new Set()); - const [selectedIntensityIds, setSelectedIntensityIds] = useState>(new Set()); const [selectedDeleteIds, setSelectedDeleteIds] = useState>(new Set()); const [step, setStep] = useState<'initial' | 'review'>('initial'); const [applying, setApplying] = useState(false); @@ -134,10 +132,10 @@ export function ReviewQuestionsDialog({ open, onOpenChange, questions, isExplici const [textResults, targetResults, deleteResults, propsResults] = await Promise.all([ analyzeQuestionText( questions.map(q => ({ ...q, intensity: q.intensity })), - isExplicit + false ), analyzeQuestionTargets(questions), - analyzeQuestionDeletions(questions, isExplicit), + analyzeQuestionDeletions(questions, false), analyzeQuestionProps( questions.map(q => ({ id: q.id, @@ -192,10 +190,6 @@ export function ReviewQuestionsDialog({ open, onOpenChange, questions, isExplici setSelectedTargetIds(new Set(meaningfulTargetResults.map(r => r.id))); setSelectedPropsIds(new Set(meaningfulPropsResults.map(r => r.id))); setSelectedDeleteIds(new Set(deleteResults.map(r => r.id))); - // Select intensity suggestions by default - setSelectedIntensityIds(new Set( - textResults.filter(r => r.suggested_intensity != null).map(r => r.id) - )); setStep('review'); } catch (error) { console.error(error); @@ -211,17 +205,15 @@ export function ReviewQuestionsDialog({ open, onOpenChange, questions, isExplici const updates: { id: string; data: Record }[] = []; const deleteIds = Array.from(selectedDeleteIds); - // Build update objects combining text, target, and intensity changes + // Build update objects combining text, target, and props changes suggestions.forEach(suggestion => { if (selectedDeleteIds.has(suggestion.questionId)) return; const textSelected = selectedTextIds.has(suggestion.questionId) && suggestion.textSuggestion; const targetSelected = selectedTargetIds.has(suggestion.questionId) && suggestion.targetSuggestion; const propsSelected = selectedPropsIds.has(suggestion.questionId) && suggestion.propsSuggestion; - const intensitySelected = selectedIntensityIds.has(suggestion.questionId) && - suggestion.textSuggestion?.suggested_intensity != null; - if (textSelected || targetSelected || propsSelected || intensitySelected) { + if (textSelected || targetSelected || propsSelected) { const updateData: Record = {}; if (textSelected && suggestion.textSuggestion) { @@ -247,10 +239,6 @@ export function ReviewQuestionsDialog({ open, onOpenChange, questions, isExplici : null; } - if (intensitySelected && suggestion.textSuggestion?.suggested_intensity != null) { - updateData.intensity = suggestion.textSuggestion.suggested_intensity; - } - if (Object.keys(updateData).length > 0) { updates.push({ id: suggestion.questionId, data: updateData }); } @@ -301,7 +289,6 @@ export function ReviewQuestionsDialog({ open, onOpenChange, questions, isExplici setSelectedTextIds(new Set()); setSelectedTargetIds(new Set()); setSelectedPropsIds(new Set()); - setSelectedIntensityIds(new Set()); setSelectedDeleteIds(new Set()); }, 300); }; @@ -327,13 +314,6 @@ export function ReviewQuestionsDialog({ open, onOpenChange, questions, isExplici setSelectedPropsIds(next); }; - const toggleIntensitySelection = (id: string) => { - const next = new Set(selectedIntensityIds); - if (next.has(id)) next.delete(id); - else next.add(id); - setSelectedIntensityIds(next); - }; - const toggleDeleteSelection = (id: string) => { const next = new Set(selectedDeleteIds); if (next.has(id)) next.delete(id); @@ -387,14 +367,12 @@ export function ReviewQuestionsDialog({ open, onOpenChange, questions, isExplici const textSuggestionCount = suggestions.filter(s => s.textSuggestion).length; const targetSuggestionCount = suggestions.filter(s => s.targetSuggestion).length; const propsSuggestionCount = suggestions.filter(s => s.propsSuggestion).length; - const intensitySuggestionCount = suggestions.filter(s => s.textSuggestion?.suggested_intensity != null).length; const deleteSuggestionCount = suggestions.filter(s => s.deleteSuggestion).length; const effectiveTextCount = Array.from(selectedTextIds).filter(id => !selectedDeleteIds.has(id)).length; const effectiveTargetCount = Array.from(selectedTargetIds).filter(id => !selectedDeleteIds.has(id)).length; const effectivePropsCount = Array.from(selectedPropsIds).filter(id => !selectedDeleteIds.has(id)).length; - const effectiveIntensityCount = Array.from(selectedIntensityIds).filter(id => !selectedDeleteIds.has(id)).length; - const totalSelectedCount = effectiveTextCount + effectiveTargetCount + effectivePropsCount + effectiveIntensityCount + selectedDeleteIds.size; - const hasSuggestions = textSuggestionCount > 0 || targetSuggestionCount > 0 || propsSuggestionCount > 0 || intensitySuggestionCount > 0 || deleteSuggestionCount > 0; + const totalSelectedCount = effectiveTextCount + effectiveTargetCount + effectivePropsCount + selectedDeleteIds.size; + const hasSuggestions = textSuggestionCount > 0 || targetSuggestionCount > 0 || propsSuggestionCount > 0 || deleteSuggestionCount > 0; // Select all/none helpers const selectAllText = () => setSelectedTextIds(new Set(suggestions.filter(s => s.textSuggestion).map(s => s.questionId))); @@ -403,24 +381,18 @@ export function ReviewQuestionsDialog({ open, onOpenChange, questions, isExplici const selectNoneTargets = () => setSelectedTargetIds(new Set()); const selectAllProps = () => setSelectedPropsIds(new Set(suggestions.filter(s => s.propsSuggestion).map(s => s.questionId))); const selectNoneProps = () => setSelectedPropsIds(new Set()); - const selectAllIntensity = () => setSelectedIntensityIds(new Set( - suggestions.filter(s => s.textSuggestion?.suggested_intensity != null).map(s => s.questionId) - )); - const selectNoneIntensity = () => setSelectedIntensityIds(new Set()); const selectAllDeletes = () => setSelectedDeleteIds(new Set(suggestions.filter(s => s.deleteSuggestion).map(s => s.questionId))); const selectNoneDeletes = () => setSelectedDeleteIds(new Set()); const selectAll = () => { selectAllText(); selectAllTargets(); selectAllProps(); - selectAllIntensity(); selectAllDeletes(); }; const selectNone = () => { selectNoneText(); selectNoneTargets(); selectNoneProps(); - selectNoneIntensity(); selectNoneDeletes(); }; @@ -462,10 +434,6 @@ export function ReviewQuestionsDialog({ open, onOpenChange, questions, isExplici Required Props
-
- - Intensity Level -
Deletion Suggestions @@ -506,10 +474,6 @@ export function ReviewQuestionsDialog({ open, onOpenChange, questions, isExplici {propsSuggestionCount} props fixes - - - {intensitySuggestionCount} intensity fixes - {deleteSuggestionCount} delete suggestions @@ -572,22 +536,6 @@ export function ReviewQuestionsDialog({ open, onOpenChange, questions, isExplici
- -
- - Intensity - {intensitySuggestionCount > 0 && ( - - )} -
-
@@ -757,50 +705,6 @@ export function ReviewQuestionsDialog({ open, onOpenChange, questions, isExplici )} - {/* Intensity Column */} - -
- - Current: {suggestion.question.intensity} - -
- {suggestion.textSuggestion?.suggested_intensity != null ? ( -
toggleIntensitySelection(suggestion.questionId)} - > -
- toggleIntensitySelection(suggestion.questionId)} - className="mt-0.5" - /> -
-
- - - {suggestion.textSuggestion.suggested_intensity} - -
- {suggestion.textSuggestion.intensity_reason && ( -
- {suggestion.textSuggestion.intensity_reason} -
- )} -
-
-
- ) : ( -
- - Correct -
- )} -
- {/* Delete Column */} {suggestion.deleteSuggestion ? ( diff --git a/apps/admin/src/components/layout/Breadcrumbs.tsx b/apps/admin/src/components/layout/Breadcrumbs.tsx index d39d161..0bd444d 100644 --- a/apps/admin/src/components/layout/Breadcrumbs.tsx +++ b/apps/admin/src/components/layout/Breadcrumbs.tsx @@ -146,26 +146,26 @@ export function Breadcrumbs() { if (breadcrumbs.length === 0 && !loading) return null; return ( -