mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 09:31:13 -05:00
Refactor: Implement Phase 3C cleanup
This commit is contained in:
@@ -32,7 +32,6 @@ export function HeroSearch() {
|
|||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
// Search functionality handled by AutocompleteSearch component in Header
|
// Search functionality handled by AutocompleteSearch component in Header
|
||||||
console.log('Search params:', searchTerm, selectedType, selectedCountry);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
|
|
||||||
const fetchItems = async (entityFilter: EntityFilter = 'all', statusFilter: StatusFilter = 'pending') => {
|
const fetchItems = async (entityFilter: EntityFilter = 'all', statusFilter: StatusFilter = 'pending') => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.log('Skipping fetch - user not authenticated');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,9 +326,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
|
|
||||||
// Sort by creation date (newest first for better UX)
|
// Sort by creation date (newest first for better UX)
|
||||||
formattedItems.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
formattedItems.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
||||||
|
|
||||||
console.log('Formatted items:', formattedItems);
|
|
||||||
console.log('Photo submissions:', formattedItems.filter(item => item.submission_type === 'photo'));
|
|
||||||
|
|
||||||
setItems(formattedItems);
|
setItems(formattedItems);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -352,7 +348,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
// Set up realtime subscriptions
|
// Set up realtime subscriptions
|
||||||
const { connectionState: submissionsConnectionState, reconnect: reconnectSubmissions } = useRealtimeSubmissions({
|
const { connectionState: submissionsConnectionState, reconnect: reconnectSubmissions } = useRealtimeSubmissions({
|
||||||
onInsert: (payload) => {
|
onInsert: (payload) => {
|
||||||
console.log('New submission received');
|
|
||||||
toast({
|
toast({
|
||||||
title: 'New Submission',
|
title: 'New Submission',
|
||||||
description: 'A new content submission has been added',
|
description: 'A new content submission has been added',
|
||||||
@@ -360,7 +355,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
fetchItems(activeEntityFilter, activeStatusFilter);
|
fetchItems(activeEntityFilter, activeStatusFilter);
|
||||||
},
|
},
|
||||||
onUpdate: (payload) => {
|
onUpdate: (payload) => {
|
||||||
console.log('Submission updated');
|
|
||||||
// Update items state directly for better UX
|
// Update items state directly for better UX
|
||||||
setItems(prevItems =>
|
setItems(prevItems =>
|
||||||
prevItems.map(item =>
|
prevItems.map(item =>
|
||||||
@@ -371,7 +365,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
onDelete: (payload) => {
|
onDelete: (payload) => {
|
||||||
console.log('Submission deleted');
|
|
||||||
setItems(prevItems =>
|
setItems(prevItems =>
|
||||||
prevItems.filter(item => !(item.id === payload.old.id && item.type === 'content_submission'))
|
prevItems.filter(item => !(item.id === payload.old.id && item.type === 'content_submission'))
|
||||||
);
|
);
|
||||||
@@ -469,7 +462,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
) => {
|
) => {
|
||||||
// Prevent multiple clicks on the same item
|
// Prevent multiple clicks on the same item
|
||||||
if (actionLoading === item.id) {
|
if (actionLoading === item.id) {
|
||||||
console.log('Action already in progress for item:', item.id);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,8 +577,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
|
|
||||||
// Handle photo submissions - create photos records when approved
|
// Handle photo submissions - create photos records when approved
|
||||||
if (action === 'approved' && item.type === 'content_submission' && item.submission_type === 'photo') {
|
if (action === 'approved' && item.type === 'content_submission' && item.submission_type === 'photo') {
|
||||||
console.log('🖼️ [PHOTO APPROVAL] Starting photo submission approval');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch photo submission from new relational tables
|
// Fetch photo submission from new relational tables
|
||||||
const { data: photoSubmission, error: fetchError } = await supabase
|
const { data: photoSubmission, error: fetchError } = await supabase
|
||||||
@@ -599,15 +589,13 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
.eq('submission_id', item.id)
|
.eq('submission_id', item.id)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
console.log('🖼️ [PHOTO APPROVAL] Fetched photo submission:', photoSubmission);
|
|
||||||
|
|
||||||
if (fetchError || !photoSubmission) {
|
if (fetchError || !photoSubmission) {
|
||||||
console.error('🖼️ [PHOTO APPROVAL] ERROR: Failed to fetch photo submission:', fetchError);
|
console.error('Failed to fetch photo submission:', fetchError);
|
||||||
throw new Error('Failed to fetch photo submission data');
|
throw new Error('Failed to fetch photo submission data');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!photoSubmission.items || photoSubmission.items.length === 0) {
|
if (!photoSubmission.items || photoSubmission.items.length === 0) {
|
||||||
console.error('🖼️ [PHOTO APPROVAL] ERROR: No photo items found');
|
console.error('No photo items found in submission');
|
||||||
throw new Error('No photos found in submission');
|
throw new Error('No photos found in submission');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,10 +605,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
.select('id')
|
.select('id')
|
||||||
.eq('submission_id', item.id);
|
.eq('submission_id', item.id);
|
||||||
|
|
||||||
console.log('🖼️ [PHOTO APPROVAL] Existing photos check:', existingPhotos);
|
|
||||||
|
|
||||||
if (existingPhotos && existingPhotos.length > 0) {
|
if (existingPhotos && existingPhotos.length > 0) {
|
||||||
console.log('🖼️ [PHOTO APPROVAL] Photos already exist for this submission, skipping creation');
|
|
||||||
|
|
||||||
// Just update submission status
|
// Just update submission status
|
||||||
const { error: updateError } = await supabase
|
const { error: updateError } = await supabase
|
||||||
@@ -650,19 +635,15 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
approved_at: new Date().toISOString(),
|
approved_at: new Date().toISOString(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('🖼️ [PHOTO APPROVAL] Creating photo records:', photoRecords);
|
|
||||||
|
|
||||||
const { data: createdPhotos, error: insertError } = await supabase
|
const { data: createdPhotos, error: insertError } = await supabase
|
||||||
.from('photos')
|
.from('photos')
|
||||||
.insert(photoRecords)
|
.insert(photoRecords)
|
||||||
.select();
|
.select();
|
||||||
|
|
||||||
if (insertError) {
|
if (insertError) {
|
||||||
console.error('🖼️ [PHOTO APPROVAL] ERROR: Failed to insert photos:', insertError);
|
console.error('Failed to insert photos:', insertError);
|
||||||
throw insertError;
|
throw insertError;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🖼️ [PHOTO APPROVAL] ✅ Successfully created photos:', createdPhotos);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update submission status
|
// Update submission status
|
||||||
@@ -677,12 +658,10 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
.eq('id', item.id);
|
.eq('id', item.id);
|
||||||
|
|
||||||
if (updateError) {
|
if (updateError) {
|
||||||
console.error('🖼️ [PHOTO APPROVAL] Error updating submission:', updateError);
|
console.error('Error updating submission:', updateError);
|
||||||
throw updateError;
|
throw updateError;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🖼️ [PHOTO APPROVAL] ✅ Complete! Photos approved and published');
|
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Photos Approved",
|
title: "Photos Approved",
|
||||||
description: `Successfully approved and published ${photoSubmission.items.length} photo(s)`,
|
description: `Successfully approved and published ${photoSubmission.items.length} photo(s)`,
|
||||||
@@ -693,8 +672,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('🖼️ [PHOTO APPROVAL] ❌ FATAL ERROR:', error);
|
console.error('Photo approval error:', error);
|
||||||
console.error('🖼️ [PHOTO APPROVAL] Error details:', error.message, error.code, error.details);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -708,8 +686,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
.in('status', ['pending', 'rejected']);
|
.in('status', ['pending', 'rejected']);
|
||||||
|
|
||||||
if (!itemsError && submissionItems && submissionItems.length > 0) {
|
if (!itemsError && submissionItems && submissionItems.length > 0) {
|
||||||
console.log(`Found ${submissionItems.length} pending submission items for ${item.id}`);
|
|
||||||
|
|
||||||
if (action === 'approved') {
|
if (action === 'approved') {
|
||||||
// Call the edge function to process all items
|
// Call the edge function to process all items
|
||||||
const { data: approvalData, error: approvalError } = await supabase.functions.invoke(
|
const { data: approvalData, error: approvalError } = await supabase.functions.invoke(
|
||||||
@@ -727,8 +703,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
throw new Error(`Failed to process submission items: ${approvalError.message}`);
|
throw new Error(`Failed to process submission items: ${approvalError.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Submission items processed successfully:', approvalData);
|
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Submission Approved",
|
title: "Submission Approved",
|
||||||
description: `Successfully processed ${submissionItems.length} item(s)`,
|
description: `Successfully processed ${submissionItems.length} item(s)`,
|
||||||
@@ -739,7 +713,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
return;
|
return;
|
||||||
} else if (action === 'rejected') {
|
} else if (action === 'rejected') {
|
||||||
// Cascade rejection to all pending items
|
// Cascade rejection to all pending items
|
||||||
console.log('Cascading rejection to submission items');
|
|
||||||
const { error: rejectError } = await supabase
|
const { error: rejectError } = await supabase
|
||||||
.from('submission_items')
|
.from('submission_items')
|
||||||
.update({
|
.update({
|
||||||
@@ -753,8 +726,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
if (rejectError) {
|
if (rejectError) {
|
||||||
console.error('Failed to cascade rejection:', rejectError);
|
console.error('Failed to cascade rejection:', rejectError);
|
||||||
// Don't fail the whole operation, just log it
|
// Don't fail the whole operation, just log it
|
||||||
} else {
|
|
||||||
console.log('Successfully cascaded rejection to submission items');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -782,8 +753,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
updateData.reviewer_notes = moderatorNotes;
|
updateData.reviewer_notes = moderatorNotes;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Updating item:', item.id, 'with data:', updateData, 'table:', table);
|
|
||||||
|
|
||||||
const { error, data } = await supabase
|
const { error, data } = await supabase
|
||||||
.from(table)
|
.from(table)
|
||||||
.update(updateData)
|
.update(updateData)
|
||||||
@@ -795,16 +764,12 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Update response:', { data, rowsAffected: data?.length });
|
|
||||||
|
|
||||||
// Check if the update actually affected any rows
|
// Check if the update actually affected any rows
|
||||||
if (!data || data.length === 0) {
|
if (!data || data.length === 0) {
|
||||||
console.error('No rows were updated. This might be due to RLS policies or the item not existing.');
|
console.error('No rows were updated. This might be due to RLS policies or the item not existing.');
|
||||||
throw new Error('Failed to update item - no rows affected. You might not have permission to moderate this content.');
|
throw new Error('Failed to update item - no rows affected. You might not have permission to moderate this content.');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Update successful, rows affected:', data.length);
|
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: `Content ${action}`,
|
title: `Content ${action}`,
|
||||||
description: `The ${item.type} has been ${action}`,
|
description: `The ${item.type} has been ${action}`,
|
||||||
@@ -824,10 +789,10 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
return newNotes;
|
return newNotes;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only refresh if we're viewing a filter that should no longer show this item
|
// Refresh if needed based on filter
|
||||||
if ((activeStatusFilter === 'pending' && (action === 'approved' || action === 'rejected')) ||
|
if ((activeStatusFilter === 'pending' && (action === 'approved' || action === 'rejected')) ||
|
||||||
(activeStatusFilter === 'flagged' && (action === 'approved' || action === 'rejected'))) {
|
(activeStatusFilter === 'flagged' && (action === 'approved' || action === 'rejected'))) {
|
||||||
console.log('Item no longer matches filter, removing from view');
|
// Item no longer matches filter
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -855,7 +820,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
|
|
||||||
// Prevent duplicate calls
|
// Prevent duplicate calls
|
||||||
if (actionLoading === item.id) {
|
if (actionLoading === item.id) {
|
||||||
console.log('Deletion already in progress for:', item.id);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,8 +829,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
setItems(prev => prev.filter(i => i.id !== item.id));
|
setItems(prev => prev.filter(i => i.id !== item.id));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Starting deletion process for submission:', item.id);
|
|
||||||
|
|
||||||
// Step 1: Extract photo IDs from the submission content
|
// Step 1: Extract photo IDs from the submission content
|
||||||
const photoIds: string[] = [];
|
const photoIds: string[] = [];
|
||||||
const validImageIds: string[] = [];
|
const validImageIds: string[] = [];
|
||||||
@@ -876,18 +838,12 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
const photosArray = item.content?.content?.photos || item.content?.photos;
|
const photosArray = item.content?.content?.photos || item.content?.photos;
|
||||||
|
|
||||||
if (photosArray && Array.isArray(photosArray)) {
|
if (photosArray && Array.isArray(photosArray)) {
|
||||||
console.log('Processing photos from content:', photosArray);
|
|
||||||
for (const photo of photosArray) {
|
for (const photo of photosArray) {
|
||||||
console.log('Processing photo object:', photo);
|
|
||||||
console.log('Photo keys:', Object.keys(photo));
|
|
||||||
console.log('photo.imageId:', photo.imageId, 'type:', typeof photo.imageId);
|
|
||||||
|
|
||||||
let imageId = '';
|
let imageId = '';
|
||||||
|
|
||||||
// First try to use the stored imageId directly
|
// First try to use the stored imageId directly
|
||||||
if (photo.imageId) {
|
if (photo.imageId) {
|
||||||
imageId = photo.imageId;
|
imageId = photo.imageId;
|
||||||
console.log('Using stored image ID:', imageId);
|
|
||||||
} else if (photo.url) {
|
} else if (photo.url) {
|
||||||
// Check if this looks like a Cloudflare image ID (not a blob URL)
|
// Check if this looks like a Cloudflare image ID (not a blob URL)
|
||||||
if (photo.url.startsWith('blob:')) {
|
if (photo.url.startsWith('blob:')) {
|
||||||
@@ -909,7 +865,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
imageId = cloudflareMatch[1];
|
imageId = cloudflareMatch[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('Extracted image ID from URL:', imageId, 'from URL:', photo.url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageId) {
|
if (imageId) {
|
||||||
@@ -922,14 +877,10 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Found ${validImageIds.length} valid image IDs to delete, ${skippedPhotos.length} photos will be orphaned`);
|
|
||||||
|
|
||||||
// Step 2: Delete photos from Cloudflare Images (if any valid IDs)
|
// Step 2: Delete photos from Cloudflare Images (if any valid IDs)
|
||||||
if (validImageIds.length > 0) {
|
if (validImageIds.length > 0) {
|
||||||
const deletePromises = validImageIds.map(async (imageId) => {
|
const deletePromises = validImageIds.map(async (imageId) => {
|
||||||
try {
|
try {
|
||||||
console.log('Attempting to delete image from Cloudflare:', imageId);
|
|
||||||
|
|
||||||
// Use Supabase SDK - automatically includes session token
|
// Use Supabase SDK - automatically includes session token
|
||||||
const { data, error } = await supabase.functions.invoke('upload-image', {
|
const { data, error } = await supabase.functions.invoke('upload-image', {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
@@ -940,8 +891,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
throw new Error(`Failed to delete image: ${error.message}`);
|
throw new Error(`Failed to delete image: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Successfully deleted image:', imageId, data);
|
|
||||||
|
|
||||||
} catch (deleteError) {
|
} catch (deleteError) {
|
||||||
console.error(`Failed to delete photo ${imageId} from Cloudflare:`, deleteError);
|
console.error(`Failed to delete photo ${imageId} from Cloudflare:`, deleteError);
|
||||||
// Continue with other deletions - don't fail the entire operation
|
// Continue with other deletions - don't fail the entire operation
|
||||||
@@ -953,7 +902,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Delete the submission from the database
|
// Step 3: Delete the submission from the database
|
||||||
console.log('Deleting submission from database:', item.id);
|
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from('content_submissions')
|
.from('content_submissions')
|
||||||
.delete()
|
.delete()
|
||||||
@@ -974,8 +922,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
if (checkData && !checkError) {
|
if (checkData && !checkError) {
|
||||||
console.error('DELETION FAILED: Item still exists in database after delete operation');
|
console.error('DELETION FAILED: Item still exists in database after delete operation');
|
||||||
throw new Error('Deletion failed - item still exists in database');
|
throw new Error('Deletion failed - item still exists in database');
|
||||||
} else {
|
|
||||||
console.log('Verified: Submission successfully deleted from database');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedCount = validImageIds.length;
|
const deletedCount = validImageIds.length;
|
||||||
@@ -1202,7 +1148,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
console.error('Failed to load review photo:', photo.url);
|
console.error('Failed to load review photo:', photo.url);
|
||||||
(e.target as HTMLImageElement).style.display = 'none';
|
(e.target as HTMLImageElement).style.display = 'none';
|
||||||
}}
|
}}
|
||||||
onLoad={() => console.log('Review photo loaded:', photo.url)}
|
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-black/50 text-white text-xs opacity-0 hover:opacity-100 transition-opacity rounded">
|
<div className="absolute inset-0 flex items-center justify-center bg-black/50 text-white text-xs opacity-0 hover:opacity-100 transition-opacity rounded">
|
||||||
<Eye className="w-4 h-4" />
|
<Eye className="w-4 h-4" />
|
||||||
@@ -1269,7 +1214,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onLoad={() => console.log('Photo submission loaded:', photo.url)}
|
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-black/50 text-white opacity-0 hover:opacity-100 transition-opacity rounded">
|
<div className="absolute inset-0 flex items-center justify-center bg-black/50 text-white opacity-0 hover:opacity-100 transition-opacity rounded">
|
||||||
<Eye className="w-5 h-5" />
|
<Eye className="w-5 h-5" />
|
||||||
|
|||||||
@@ -275,12 +275,9 @@ export function PhotoUpload({
|
|||||||
console.error('Failed to load avatar image:', uploadedImages[0].thumbnailUrl);
|
console.error('Failed to load avatar image:', uploadedImages[0].thumbnailUrl);
|
||||||
e.currentTarget.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSIjZjNmNGY2Ii8+CjxwYXRoIGQ9Im0xNSAxMi0zLTMtMy4wMDEgM0w2IDlsNi02aDZ2NloiIGZpbGw9IiM5Y2EzYWYiLz4KPC9zdmc+';
|
e.currentTarget.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSIjZjNmNGY2Ii8+CjxwYXRoIGQ9Im0xNSAxMi0zLTMtMy4wMDEgM0w2IDlsNi02aDZ2NloiIGZpbGw9IiM5Y2EzYWYiLz4KPC9zdmc+';
|
||||||
}}
|
}}
|
||||||
onLoad={() => {
|
|
||||||
console.log('Avatar image loaded successfully:', uploadedImages[0].thumbnailUrl);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
) : existingPhotos.length > 0 ? (
|
) : existingPhotos.length > 0 ? (
|
||||||
<img
|
<img
|
||||||
src={existingPhotos[0]}
|
src={existingPhotos[0]}
|
||||||
alt="Avatar"
|
alt="Avatar"
|
||||||
className="w-24 h-24 rounded-full object-cover border-2 border-border"
|
className="w-24 h-24 rounded-full object-cover border-2 border-border"
|
||||||
@@ -426,9 +423,6 @@ export function PhotoUpload({
|
|||||||
console.error('Failed to load image:', image.thumbnailUrl);
|
console.error('Failed to load image:', image.thumbnailUrl);
|
||||||
e.currentTarget.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSIjZjNmNGY2Ii8+CjxwYXRoIGQ9Im0xNSAxMi0zLTMtMy4wMDEgM0w2IDlsNi02aDZ2NloiIGZpbGw9IiM5Y2EzYWYiLz4KPC9zdmc+';
|
e.currentTarget.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSIjZjNmNGY2Ii8+CjxwYXRoIGQ9Im0xNSAxMi0zLTMtMy4wMDEgM0w2IDlsNi02aDZ2NloiIGZpbGw9IiM5Y2EzYWYiLz4KPC9zdmc+';
|
||||||
}}
|
}}
|
||||||
onLoad={() => {
|
|
||||||
console.log('Image loaded successfully:', image.thumbnailUrl);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
@@ -464,9 +458,6 @@ export function PhotoUpload({
|
|||||||
console.error('Failed to load existing image:', url);
|
console.error('Failed to load existing image:', url);
|
||||||
e.currentTarget.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSIjZjNmNGY2Ii8+CjxwYXRoIGQ9Im0xNSAxMi0zLTMtMy4wMDEgM0w2IDlsNi02aDZ2NloiIGZpbGw9IiM5Y2EzYWYiLz4KPC9zdmc+';
|
e.currentTarget.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSIjZjNmNGY2Ii8+CjxwYXRoIGQ9Im0xNSAxMi0zLTMtMy4wMDEgM0w2IDlsNi02aDZ2NloiIGZpbGw9IiM5Y2EzYWYiLz4KPC9zdmc+';
|
||||||
}}
|
}}
|
||||||
onLoad={() => {
|
|
||||||
console.log('Existing image loaded successfully:', url);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Badge variant="outline" className="absolute bottom-2 left-2 text-xs">
|
<Badge variant="outline" className="absolute bottom-2 left-2 text-xs">
|
||||||
Existing
|
Existing
|
||||||
|
|||||||
@@ -81,8 +81,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
currentEmail !== previousEmail &&
|
currentEmail !== previousEmail &&
|
||||||
!newEmailPending
|
!newEmailPending
|
||||||
) {
|
) {
|
||||||
console.log('Email change confirmed:', { from: previousEmail, to: currentEmail });
|
|
||||||
|
|
||||||
// Defer Novu update and notifications to avoid blocking auth
|
// Defer Novu update and notifications to avoid blocking auth
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { Database } from '@/integrations/supabase/types';
|
import { Database } from '@/integrations/supabase/types';
|
||||||
|
import type {
|
||||||
|
ParkSubmissionData,
|
||||||
|
RideSubmissionData,
|
||||||
|
CompanySubmissionData,
|
||||||
|
RideModelSubmissionData,
|
||||||
|
} from '@/types/submission-data';
|
||||||
|
|
||||||
type ParkInsert = Database['public']['Tables']['parks']['Insert'];
|
type ParkInsert = Database['public']['Tables']['parks']['Insert'];
|
||||||
type RideInsert = Database['public']['Tables']['rides']['Insert'];
|
type RideInsert = Database['public']['Tables']['rides']['Insert'];
|
||||||
@@ -7,8 +13,10 @@ type RideModelInsert = Database['public']['Tables']['ride_models']['Insert'];
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform park submission data to database insert format
|
* Transform park submission data to database insert format
|
||||||
|
* @param submissionData - Validated park submission data
|
||||||
|
* @returns Database insert object for parks table
|
||||||
*/
|
*/
|
||||||
export function transformParkData(submissionData: any): ParkInsert {
|
export function transformParkData(submissionData: ParkSubmissionData): ParkInsert {
|
||||||
return {
|
return {
|
||||||
name: submissionData.name,
|
name: submissionData.name,
|
||||||
slug: submissionData.slug,
|
slug: submissionData.slug,
|
||||||
@@ -38,8 +46,10 @@ export function transformParkData(submissionData: any): ParkInsert {
|
|||||||
* Transform ride submission data to database insert format
|
* Transform ride submission data to database insert format
|
||||||
* Note: Relational data (technical_specs, coaster_stats, former_names) are now
|
* Note: Relational data (technical_specs, coaster_stats, former_names) are now
|
||||||
* stored in separate tables and should not be included in the main ride insert.
|
* stored in separate tables and should not be included in the main ride insert.
|
||||||
|
* @param submissionData - Validated ride submission data
|
||||||
|
* @returns Database insert object for rides table
|
||||||
*/
|
*/
|
||||||
export function transformRideData(submissionData: any): RideInsert {
|
export function transformRideData(submissionData: RideSubmissionData): RideInsert {
|
||||||
return {
|
return {
|
||||||
name: submissionData.name,
|
name: submissionData.name,
|
||||||
slug: submissionData.slug,
|
slug: submissionData.slug,
|
||||||
@@ -78,9 +88,12 @@ export function transformRideData(submissionData: any): RideInsert {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform company submission data to database insert format
|
* Transform company submission data to database insert format
|
||||||
|
* @param submissionData - Validated company submission data
|
||||||
|
* @param companyType - Type of company (manufacturer, operator, property_owner, designer)
|
||||||
|
* @returns Database insert object for companies table
|
||||||
*/
|
*/
|
||||||
export function transformCompanyData(
|
export function transformCompanyData(
|
||||||
submissionData: any,
|
submissionData: CompanySubmissionData,
|
||||||
companyType: 'manufacturer' | 'operator' | 'property_owner' | 'designer'
|
companyType: 'manufacturer' | 'operator' | 'property_owner' | 'designer'
|
||||||
): CompanyInsert {
|
): CompanyInsert {
|
||||||
return {
|
return {
|
||||||
@@ -102,8 +115,10 @@ export function transformCompanyData(
|
|||||||
* Transform ride model submission data to database insert format
|
* Transform ride model submission data to database insert format
|
||||||
* Note: Technical specifications are now stored in the ride_model_technical_specifications
|
* Note: Technical specifications are now stored in the ride_model_technical_specifications
|
||||||
* table and should not be included in the main ride model insert.
|
* table and should not be included in the main ride model insert.
|
||||||
|
* @param submissionData - Validated ride model submission data
|
||||||
|
* @returns Database insert object for ride_models table
|
||||||
*/
|
*/
|
||||||
export function transformRideModelData(submissionData: any): RideModelInsert {
|
export function transformRideModelData(submissionData: RideModelSubmissionData): RideModelInsert {
|
||||||
return {
|
return {
|
||||||
name: submissionData.name,
|
name: submissionData.name,
|
||||||
slug: submissionData.slug,
|
slug: submissionData.slug,
|
||||||
@@ -150,8 +165,14 @@ export function extractImageId(url: string): string {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate and sanitize submission data before transformation
|
* Validate and sanitize submission data before transformation
|
||||||
|
* @param data - Submission data to validate
|
||||||
|
* @param itemType - Type of entity being validated (for error messages)
|
||||||
|
* @throws Error if validation fails
|
||||||
*/
|
*/
|
||||||
export function validateSubmissionData(data: any, itemType: string): void {
|
export function validateSubmissionData(
|
||||||
|
data: ParkSubmissionData | RideSubmissionData | CompanySubmissionData | RideModelSubmissionData,
|
||||||
|
itemType: string
|
||||||
|
): void {
|
||||||
if (!data.name || typeof data.name !== 'string' || data.name.trim() === '') {
|
if (!data.name || typeof data.name !== 'string' || data.name.trim() === '') {
|
||||||
throw new Error(`${itemType} name is required`);
|
throw new Error(`${itemType} name is required`);
|
||||||
}
|
}
|
||||||
|
|||||||
85
src/types/submission-data.ts
Normal file
85
src/types/submission-data.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* Type definitions for submission data structures
|
||||||
|
* These replace the `any` types in entityTransformers.ts
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ParkSubmissionData {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
description?: string | null;
|
||||||
|
park_type: string;
|
||||||
|
status: string;
|
||||||
|
opening_date?: string | null;
|
||||||
|
closing_date?: string | null;
|
||||||
|
website_url?: string | null;
|
||||||
|
phone?: string | null;
|
||||||
|
email?: string | null;
|
||||||
|
operator_id?: string | null;
|
||||||
|
property_owner_id?: string | null;
|
||||||
|
location_id?: string | null;
|
||||||
|
banner_image_url?: string | null;
|
||||||
|
banner_image_id?: string | null;
|
||||||
|
card_image_url?: string | null;
|
||||||
|
card_image_id?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RideSubmissionData {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
description?: string | null;
|
||||||
|
category: string;
|
||||||
|
ride_sub_type?: string | null;
|
||||||
|
status: string;
|
||||||
|
park_id: string;
|
||||||
|
ride_model_id?: string | null;
|
||||||
|
manufacturer_id?: string | null;
|
||||||
|
designer_id?: string | null;
|
||||||
|
opening_date?: string | null;
|
||||||
|
closing_date?: string | null;
|
||||||
|
height_requirement?: number | null;
|
||||||
|
age_requirement?: number | null;
|
||||||
|
capacity_per_hour?: number | null;
|
||||||
|
duration_seconds?: number | null;
|
||||||
|
max_speed_kmh?: number | null;
|
||||||
|
max_height_meters?: number | null;
|
||||||
|
length_meters?: number | null;
|
||||||
|
drop_height_meters?: number | null;
|
||||||
|
inversions?: number | null;
|
||||||
|
max_g_force?: number | null;
|
||||||
|
coaster_type?: string | null;
|
||||||
|
seating_type?: string | null;
|
||||||
|
intensity_level?: string | null;
|
||||||
|
banner_image_url?: string | null;
|
||||||
|
banner_image_id?: string | null;
|
||||||
|
card_image_url?: string | null;
|
||||||
|
card_image_id?: string | null;
|
||||||
|
image_url?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompanySubmissionData {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
description?: string | null;
|
||||||
|
person_type?: 'company' | 'individual';
|
||||||
|
founded_year?: number | null;
|
||||||
|
headquarters_location?: string | null;
|
||||||
|
website_url?: string | null;
|
||||||
|
logo_url?: string | null;
|
||||||
|
banner_image_url?: string | null;
|
||||||
|
banner_image_id?: string | null;
|
||||||
|
card_image_url?: string | null;
|
||||||
|
card_image_id?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RideModelSubmissionData {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
manufacturer_id: string;
|
||||||
|
category: string;
|
||||||
|
ride_type?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
banner_image_url?: string | null;
|
||||||
|
banner_image_id?: string | null;
|
||||||
|
card_image_url?: string | null;
|
||||||
|
card_image_id?: string | null;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user