From b0d3af234eee177edda4ee20e450514255086574 Mon Sep 17 00:00:00 2001
From: "gpt-engineer-app[bot]"
<159125892+gpt-engineer-app[bot]@users.noreply.github.com>
Date: Tue, 30 Sep 2025 14:07:38 +0000
Subject: [PATCH] Reverted to commit dc3c210e4075011351b6c4c1cdfe16ad417ce3dc
---
src/components/moderation/ItemEditDialog.tsx | 215 ++++++++++++++++++
src/components/moderation/ItemReviewCard.tsx | 5 +
.../moderation/SubmissionReviewManager.tsx | 21 +-
src/lib/submissionItemsService.ts | 96 ++++++++
4 files changed, 336 insertions(+), 1 deletion(-)
create mode 100644 src/components/moderation/ItemEditDialog.tsx
diff --git a/src/components/moderation/ItemEditDialog.tsx b/src/components/moderation/ItemEditDialog.tsx
new file mode 100644
index 00000000..c1ddfee2
--- /dev/null
+++ b/src/components/moderation/ItemEditDialog.tsx
@@ -0,0 +1,215 @@
+import { useState } from 'react';
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
+import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Button } from '@/components/ui/button';
+import { ParkForm } from '@/components/admin/ParkForm';
+import { RideForm } from '@/components/admin/RideForm';
+import { ManufacturerForm } from '@/components/admin/ManufacturerForm';
+import { DesignerForm } from '@/components/admin/DesignerForm';
+import { OperatorForm } from '@/components/admin/OperatorForm';
+import { PropertyOwnerForm } from '@/components/admin/PropertyOwnerForm';
+import { RideModelForm } from '@/components/admin/RideModelForm';
+import { PhotoCaptionEditor } from '@/components/upload/PhotoCaptionEditor';
+import { useIsMobile } from '@/hooks/use-mobile';
+import { editSubmissionItem } from '@/lib/submissionItemsService';
+import { useAuth } from '@/hooks/useAuth';
+import { toast } from '@/hooks/use-toast';
+import type { SubmissionItemWithDeps } from '@/lib/submissionItemsService';
+
+interface ItemEditDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ item: SubmissionItemWithDeps | null;
+ onEditComplete: () => void;
+}
+
+export function ItemEditDialog({ open, onOpenChange, item, onEditComplete }: ItemEditDialogProps) {
+ const isMobile = useIsMobile();
+ const { user } = useAuth();
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ if (!item) return null;
+
+ const handleFormSubmit = async (data: any) => {
+ if (!user) {
+ toast({
+ title: "Authentication Required",
+ description: "You must be logged in to edit items.",
+ variant: "destructive"
+ });
+ return;
+ }
+
+ setIsSubmitting(true);
+ try {
+ await editSubmissionItem(item.id, data, user.id);
+
+ toast({
+ title: "Item Updated",
+ description: "The submission item has been updated successfully."
+ });
+
+ onOpenChange(false);
+ onEditComplete();
+ } catch (error: any) {
+ toast({
+ title: "Error",
+ description: error.message || "Failed to update submission item.",
+ variant: "destructive"
+ });
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ const handleCancel = () => {
+ if (!isSubmitting) {
+ onOpenChange(false);
+ }
+ };
+
+ const renderForm = () => {
+ const itemData = item.item_data as any;
+
+ switch (item.item_type) {
+ case 'park':
+ return (
+
+ );
+
+ case 'ride':
+ return (
+
+ );
+
+ case 'manufacturer':
+ return (
+
+ );
+
+ case 'designer':
+ return (
+
+ );
+
+ case 'operator':
+ return (
+
+ );
+
+ case 'property_owner':
+ return (
+
+ );
+
+ case 'ride_model':
+ return (
+
+ );
+
+ case 'photo':
+ return (
+
+
+
+ {
+ itemData.caption = e.target.value;
+ }}
+ placeholder="Enter photo caption"
+ />
+
+ {itemData.image_url && (
+

+ )}
+
+
+
+
+
+ );
+
+ default:
+ return (
+
+ Form not available for item type: {item.item_type}
+
+ );
+ }
+ };
+
+ const content = (
+ <>
+ {isMobile ? (
+
+ Edit {item.item_type.replace('_', ' ')}
+
+ ) : (
+
+ Edit {item.item_type.replace('_', ' ')}
+
+ )}
+
+ {renderForm()}
+
+ >
+ );
+
+ if (isMobile) {
+ return (
+
+
+ {content}
+
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/src/components/moderation/ItemReviewCard.tsx b/src/components/moderation/ItemReviewCard.tsx
index 0e6ce77a..9e29dd4b 100644
--- a/src/components/moderation/ItemReviewCard.tsx
+++ b/src/components/moderation/ItemReviewCard.tsx
@@ -141,6 +141,11 @@ export function ItemReviewCard({ item, onEdit, onStatusChange }: ItemReviewCardP
)}
+ {item.original_data && (
+
+ Edited
+
+ )}
diff --git a/src/components/moderation/SubmissionReviewManager.tsx b/src/components/moderation/SubmissionReviewManager.tsx
index ac8a7f0c..2f67f5b8 100644
--- a/src/components/moderation/SubmissionReviewManager.tsx
+++ b/src/components/moderation/SubmissionReviewManager.tsx
@@ -27,6 +27,7 @@ import { DependencyVisualizer } from './DependencyVisualizer';
import { ConflictResolutionDialog } from './ConflictResolutionDialog';
import { EscalationDialog } from './EscalationDialog';
import { RejectionDialog } from './RejectionDialog';
+import { ItemEditDialog } from './ItemEditDialog';
interface SubmissionReviewManagerProps {
submissionId: string;
@@ -48,6 +49,8 @@ export function SubmissionReviewManager({
const [showConflictDialog, setShowConflictDialog] = useState(false);
const [showEscalationDialog, setShowEscalationDialog] = useState(false);
const [showRejectionDialog, setShowRejectionDialog] = useState(false);
+ const [showEditDialog, setShowEditDialog] = useState(false);
+ const [editingItem, setEditingItem] = useState(null);
const [activeTab, setActiveTab] = useState<'items' | 'dependencies'>('items');
const { toast } = useToast();
@@ -240,6 +243,12 @@ export function SubmissionReviewManager({
}
};
+ const handleEditComplete = async () => {
+ // Reload items after edit
+ await loadSubmissionItems();
+ setEditingItem(null);
+ };
+
const pendingCount = items.filter(item => item.status === 'pending').length;
const selectedCount = selectedItemIds.size;
@@ -292,6 +301,13 @@ export function SubmissionReviewManager({
)}
onReject={handleReject}
/>
+
+
>
);
@@ -331,7 +347,10 @@ export function SubmissionReviewManager({
/>
{/* TODO: Implement editing */}}
+ onEdit={() => {
+ setEditingItem(item);
+ setShowEditDialog(true);
+ }}
onStatusChange={(status) => {/* TODO: Update status */}}
/>
diff --git a/src/lib/submissionItemsService.ts b/src/lib/submissionItemsService.ts
index 416e0189..7a9b29fa 100644
--- a/src/lib/submissionItemsService.ts
+++ b/src/lib/submissionItemsService.ts
@@ -357,6 +357,102 @@ export async function resolveConflicts(
return { updatedItems, newConflicts };
}
+/**
+ * Edit a submission item
+ * Moderators can edit directly, regular users trigger auto-escalation
+ */
+export async function editSubmissionItem(
+ itemId: string,
+ newData: any,
+ userId: string
+): Promise {
+ // Check user permissions
+ const { data: userRoles } = await supabase
+ .from('user_roles')
+ .select('role')
+ .eq('user_id', userId);
+
+ const isModerator = userRoles?.some(r =>
+ ['moderator', 'admin', 'superuser'].includes(r.role)
+ );
+
+ // Get current item
+ const { data: currentItem, error: fetchError } = await supabase
+ .from('submission_items')
+ .select('*')
+ .eq('id', itemId)
+ .single();
+
+ if (fetchError || !currentItem) {
+ throw new Error('Failed to fetch submission item');
+ }
+
+ // Preserve original data if not already saved
+ const originalData = currentItem.original_data || currentItem.item_data;
+
+ if (isModerator) {
+ // Moderators can edit directly
+ const { error } = await supabase
+ .from('submission_items')
+ .update({
+ item_data: newData,
+ original_data: originalData,
+ updated_at: new Date().toISOString()
+ })
+ .eq('id', itemId);
+
+ if (error) {
+ console.error('Error updating submission item:', error);
+ throw new Error(`Failed to update item: ${error.message}`);
+ }
+
+ // Log the edit
+ await supabase
+ .from('admin_audit_log')
+ .insert({
+ admin_user_id: userId,
+ target_user_id: currentItem.submission_id,
+ action: 'edit_submission_item',
+ details: {
+ item_id: itemId,
+ item_type: currentItem.item_type,
+ changes: newData
+ }
+ });
+ } else {
+ // Regular users trigger auto-escalation
+ const { data: submission } = await supabase
+ .from('content_submissions')
+ .select('id, status')
+ .eq('id', currentItem.submission_id)
+ .single();
+
+ if (submission && submission.status !== 'escalated') {
+ await escalateSubmission(
+ currentItem.submission_id,
+ `User requested edit for ${currentItem.item_type} item`,
+ userId
+ );
+ }
+
+ // Update item with edit request
+ const { error } = await supabase
+ .from('submission_items')
+ .update({
+ item_data: newData,
+ original_data: originalData,
+ status: 'pending',
+ updated_at: new Date().toISOString()
+ })
+ .eq('id', itemId);
+
+ if (error) {
+ console.error('Error updating submission item:', error);
+ throw new Error(`Failed to update item: ${error.message}`);
+ }
+ }
+}
+
/**
* Update individual submission item status
*/