Fix critical Phase 1 issues

This commit is contained in:
gpt-engineer-app[bot]
2025-10-15 12:04:41 +00:00
parent 21d16f01ed
commit 0434aa95ee
3 changed files with 484 additions and 28 deletions

View File

@@ -23,9 +23,50 @@ import { useAuth } from '@/hooks/useAuth';
import { useIsMobile } from '@/hooks/use-mobile';
import { smartMergeArray } from '@/lib/smartStateUpdate';
// Type-safe reported content interfaces
interface ReportedReview {
id: string;
title: string | null;
content: string | null;
rating: number;
}
interface ReportedProfile {
user_id: string;
username: string;
display_name: string | null;
}
interface ReportedSubmission {
id: string;
submission_type: string;
status: string;
}
// Union type for all possible reported content
type ReportedContent = ReportedReview | ReportedProfile | ReportedSubmission | null;
// Discriminated union for entity types
type ReportEntityType = 'review' | 'profile' | 'content_submission';
/**
* Type guards for reported content
*/
function isReportedReview(content: ReportedContent): content is ReportedReview {
return content !== null && 'rating' in content;
}
function isReportedProfile(content: ReportedContent): content is ReportedProfile {
return content !== null && 'username' in content;
}
function isReportedSubmission(content: ReportedContent): content is ReportedSubmission {
return content !== null && 'submission_type' in content;
}
interface Report {
id: string;
reported_entity_type: string;
reported_entity_type: ReportEntityType;
reported_entity_id: string;
report_type: string;
reason: string;
@@ -35,7 +76,7 @@ interface Report {
username: string;
display_name?: string;
};
reported_content?: any;
reported_content?: ReportedContent;
}
const REPORT_TYPE_LABELS = {
@@ -161,28 +202,79 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
const profileMap = new Map(profiles?.map(p => [p.user_id, p]) || []);
// Fetch the reported content for each report
const reportsWithContent = await Promise.all(
(data || []).map(async (report) => {
let reportedContent = null;
if (report.reported_entity_type === 'review') {
const { data: reviewData } = await supabase
// Batch fetch reported content to avoid N+1 queries
// Separate entity IDs by type for efficient batching
const reviewIds = data?.filter(r => r.reported_entity_type === 'review')
.map(r => r.reported_entity_id) || [];
const profileIds = data?.filter(r => r.reported_entity_type === 'profile')
.map(r => r.reported_entity_id) || [];
const submissionIds = data?.filter(r => r.reported_entity_type === 'content_submission')
.map(r => r.reported_entity_id) || [];
// Parallel batch fetch for all entity types
const [reviewsData, profilesData, submissionsData] = await Promise.all([
reviewIds.length > 0
? supabase
.from('reviews')
.select('title, content, rating')
.eq('id', report.reported_entity_id)
.single();
reportedContent = reviewData;
}
// Add other entity types as needed
return {
...report,
reporter_profile: profileMap.get(report.reporter_id),
reported_content: reportedContent,
};
})
.select('id, title, content, rating')
.in('id', reviewIds)
.then(({ data }) => data || [])
: Promise.resolve([]),
profileIds.length > 0
? supabase
.from('profiles')
.select('user_id, username, display_name')
.in('user_id', profileIds)
.then(({ data }) => data || [])
: Promise.resolve([]),
submissionIds.length > 0
? supabase
.from('content_submissions')
.select('id, submission_type, status')
.in('id', submissionIds)
.then(({ data }) => data || [])
: Promise.resolve([]),
]);
// Create lookup maps for O(1) access
const reviewMap = new Map(
reviewsData.map(r => [r.id, r as ReportedReview] as const)
);
const profilesMap = new Map(
profilesData.map(p => [p.user_id, p as ReportedProfile] as const)
);
const submissionsMap = new Map(
submissionsData.map(s => [s.id, s as ReportedSubmission] as const)
);
// Map reports to their content (O(n) instead of O(n*m))
const reportsWithContent: Report[] = (data || []).map(report => {
let reportedContent: ReportedContent = null;
// Type-safe entity type handling
const entityType = report.reported_entity_type as ReportEntityType;
switch (entityType) {
case 'review':
reportedContent = reviewMap.get(report.reported_entity_id) || null;
break;
case 'profile':
reportedContent = profilesMap.get(report.reported_entity_id) || null;
break;
case 'content_submission':
reportedContent = submissionsMap.get(report.reported_entity_id) || null;
break;
}
return {
...report,
reported_entity_type: entityType,
reporter_profile: profileMap.get(report.reporter_id),
reported_content: reportedContent,
};
});
// Use smart merging for silent refreshes if strategy is 'merge'
if (silent && refreshStrategy === 'merge') {
@@ -474,7 +566,7 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
<div>
<Label>Reported Content:</Label>
<div className="bg-destructive/5 border border-destructive/20 p-4 rounded-lg mt-1">
{report.reported_entity_type === 'review' && (
{report.reported_entity_type === 'review' && isReportedReview(report.reported_content) && (
<div>
{report.reported_content.title && (
<h4 className="font-semibold mb-2">{report.reported_content.title}</h4>
@@ -487,6 +579,28 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
</div>
</div>
)}
{report.reported_entity_type === 'profile' && isReportedProfile(report.reported_content) && (
<div>
<div className="font-semibold mb-2">
{report.reported_content.display_name || report.reported_content.username}
</div>
<div className="text-sm text-muted-foreground">
@{report.reported_content.username}
</div>
</div>
)}
{report.reported_entity_type === 'content_submission' && isReportedSubmission(report.reported_content) && (
<div>
<div className="text-sm">
<span className="font-semibold">Type:</span> {report.reported_content.submission_type}
</div>
<div className="text-sm text-muted-foreground">
<span className="font-semibold">Status:</span> {report.reported_content.status}
</div>
</div>
)}
</div>
</div>
)}