mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:11:13 -05:00
Fix internal error
This commit is contained in:
@@ -97,7 +97,7 @@ export function ActivityCard({ activity }: ActivityCardProps) {
|
||||
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Avatar className="w-5 h-5">
|
||||
<AvatarImage src={activity.moderator?.avatar_url} />
|
||||
<AvatarImage src={activity.moderator?.avatar_url || undefined} />
|
||||
<AvatarFallback className="text-xs">{moderatorInitial}</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="truncate">
|
||||
|
||||
@@ -200,7 +200,7 @@ export const EntityEditPreview = ({ submissionId, entityType, entityName }: Enti
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{itemData?.cloudflare_image_url && typeof itemData.cloudflare_image_url === 'string' && (
|
||||
{(itemData?.cloudflare_image_url && typeof itemData.cloudflare_image_url === 'string' && (
|
||||
<Card className="overflow-hidden">
|
||||
<CardContent className="p-2">
|
||||
<img
|
||||
@@ -210,7 +210,7 @@ export const EntityEditPreview = ({ submissionId, entityType, entityName }: Enti
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
)) as React.ReactNode}
|
||||
|
||||
{isEdit && (
|
||||
<div className="space-y-2 text-sm">
|
||||
@@ -229,12 +229,12 @@ export const EntityEditPreview = ({ submissionId, entityType, entityName }: Enti
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isEdit && itemData?.reason && typeof itemData.reason === 'string' && (
|
||||
{(!isEdit && itemData?.reason && typeof itemData.reason === 'string' && (
|
||||
<div className="text-sm">
|
||||
<span className="font-medium">Reason: </span>
|
||||
<span className="text-muted-foreground">{itemData.reason}</span>
|
||||
</div>
|
||||
)}
|
||||
)) as React.ReactNode}
|
||||
|
||||
<div className="text-xs text-muted-foreground italic">
|
||||
Click "Review Items" for full details
|
||||
@@ -244,7 +244,7 @@ export const EntityEditPreview = ({ submissionId, entityType, entityName }: Enti
|
||||
}
|
||||
|
||||
// Build photos array for modal
|
||||
const photos = [];
|
||||
const photos: Array<{ id: string; url: string; caption: string | null }> = [];
|
||||
if (bannerImageUrl) {
|
||||
photos.push({
|
||||
id: 'banner',
|
||||
@@ -336,7 +336,10 @@ export const EntityEditPreview = ({ submissionId, entityType, entityName }: Enti
|
||||
</div>
|
||||
|
||||
<PhotoModal
|
||||
photos={photos}
|
||||
photos={photos.map(photo => ({
|
||||
...photo,
|
||||
caption: photo.caption ?? undefined
|
||||
}))}
|
||||
initialIndex={selectedImageIndex}
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
|
||||
@@ -221,11 +221,11 @@ export function LocationDiff({ oldLocation, newLocation, compact = false }: Loca
|
||||
}
|
||||
|
||||
if (typeof loc === 'object') {
|
||||
const parts = [];
|
||||
if (loc.city) parts.push(loc.city);
|
||||
if (loc.state_province) parts.push(loc.state_province);
|
||||
if (loc.country && loc.country !== loc.state_province) parts.push(loc.country);
|
||||
if (loc.postal_code) parts.push(loc.postal_code);
|
||||
const parts: string[] = [];
|
||||
if (loc.city) parts.push(String(loc.city));
|
||||
if (loc.state_province) parts.push(String(loc.state_province));
|
||||
if (loc.country && loc.country !== loc.state_province) parts.push(String(loc.country));
|
||||
if (loc.postal_code) parts.push(String(loc.postal_code));
|
||||
|
||||
let locationStr = parts.join(', ') || 'Unknown';
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Textarea } from '@/components/ui/textarea';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
@@ -93,6 +94,11 @@ export function ItemEditDialog({ item, items, open, onOpenChange, onComplete }:
|
||||
};
|
||||
|
||||
const handlePhotoSubmit = async (caption: string, credit: string) => {
|
||||
if (!item?.item_data) {
|
||||
logger.error('No item data available for photo submission');
|
||||
return;
|
||||
}
|
||||
|
||||
const itemData = typeof item.item_data === 'object' && item.item_data !== null && !Array.isArray(item.item_data)
|
||||
? item.item_data as Record<string, unknown>
|
||||
: {};
|
||||
|
||||
@@ -86,12 +86,12 @@ export function ItemReviewCard({ item, onEdit, onStatusChange, submissionId }: I
|
||||
<CardTitle className={isMobile ? "text-sm" : "text-base"}>
|
||||
{item.item_type.replace('_', ' ').toUpperCase()}
|
||||
</CardTitle>
|
||||
{item.original_data && Object.keys(item.original_data).length > 0 && (
|
||||
{(item.original_data && Object.keys(item.original_data).length > 0 && (
|
||||
<Badge variant="secondary" className="text-xs bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300 border border-blue-300 dark:border-blue-700">
|
||||
<Edit className="w-3 h-3 mr-1" />
|
||||
Moderator Edited
|
||||
</Badge>
|
||||
)}
|
||||
)) as React.ReactNode}
|
||||
{hasBlockingErrors && (
|
||||
<Badge variant="destructive" className="text-xs">
|
||||
Blocked
|
||||
|
||||
@@ -315,7 +315,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef, ModerationQueuePro
|
||||
activeEntityFilter={queueManager.filters.entityFilter}
|
||||
activeStatusFilter={queueManager.filters.statusFilter}
|
||||
sortConfig={queueManager.filters.sortConfig}
|
||||
isMobile={isMobile}
|
||||
isMobile={isMobile ?? false}
|
||||
isLoading={queueManager.loadingState === 'loading'}
|
||||
onEntityFilterChange={queueManager.filters.setEntityFilter}
|
||||
onStatusFilterChange={queueManager.filters.setStatusFilter}
|
||||
@@ -367,11 +367,11 @@ export const ModerationQueue = forwardRef<ModerationQueueRef, ModerationQueuePro
|
||||
<ModerationErrorBoundary key={item.id} submissionId={item.id}>
|
||||
<QueueItem
|
||||
item={item}
|
||||
isMobile={isMobile}
|
||||
isMobile={isMobile ?? false}
|
||||
actionLoading={queueManager.actionLoading}
|
||||
isLockedByMe={queueManager.queue.isLockedByMe(item.id, item.assigned_to, item.locked_until)}
|
||||
isLockedByOther={queueManager.queue.isLockedByOther(item.id, item.assigned_to, item.locked_until)}
|
||||
lockStatus={getLockStatus({ assigned_to: item.assigned_to, locked_until: item.locked_until }, user?.id || '')}
|
||||
isLockedByMe={queueManager.queue.isLockedByMe(item.id, item.assigned_to || null, item.locked_until || null)}
|
||||
isLockedByOther={queueManager.queue.isLockedByOther(item.id, item.assigned_to || null, item.locked_until || null)}
|
||||
lockStatus={getLockStatus({ assigned_to: item.assigned_to || null, locked_until: item.locked_until || null }, user?.id || '')}
|
||||
currentLockSubmissionId={queueManager.queue.currentLock?.submissionId}
|
||||
notes={notes}
|
||||
isAdmin={isAdmin()}
|
||||
@@ -428,11 +428,11 @@ export const ModerationQueue = forwardRef<ModerationQueueRef, ModerationQueuePro
|
||||
<ModerationErrorBoundary submissionId={item.id}>
|
||||
<QueueItem
|
||||
item={item}
|
||||
isMobile={isMobile}
|
||||
isMobile={isMobile ?? false}
|
||||
actionLoading={queueManager.actionLoading}
|
||||
isLockedByMe={queueManager.queue.isLockedByMe(item.id, item.assigned_to, item.locked_until)}
|
||||
isLockedByOther={queueManager.queue.isLockedByOther(item.id, item.assigned_to, item.locked_until)}
|
||||
lockStatus={getLockStatus({ assigned_to: item.assigned_to, locked_until: item.locked_until }, user?.id || '')}
|
||||
isLockedByMe={queueManager.queue.isLockedByMe(item.id, item.assigned_to || null, item.locked_until || null)}
|
||||
isLockedByOther={queueManager.queue.isLockedByOther(item.id, item.assigned_to || null, item.locked_until || null)}
|
||||
lockStatus={getLockStatus({ assigned_to: item.assigned_to || null, locked_until: item.locked_until || null }, user?.id || '')}
|
||||
currentLockSubmissionId={queueManager.queue.currentLock?.submissionId}
|
||||
notes={notes}
|
||||
isAdmin={isAdmin()}
|
||||
@@ -467,7 +467,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef, ModerationQueuePro
|
||||
totalPages={queueManager.pagination.totalPages}
|
||||
pageSize={queueManager.pagination.pageSize}
|
||||
totalCount={queueManager.pagination.totalCount}
|
||||
isMobile={isMobile}
|
||||
isMobile={isMobile ?? false}
|
||||
onPageChange={queueManager.pagination.setCurrentPage}
|
||||
onPageSizeChange={queueManager.pagination.setPageSize}
|
||||
/>
|
||||
@@ -475,7 +475,10 @@ export const ModerationQueue = forwardRef<ModerationQueueRef, ModerationQueuePro
|
||||
|
||||
{/* Modals */}
|
||||
<PhotoModal
|
||||
photos={selectedPhotos}
|
||||
photos={selectedPhotos.map(photo => ({
|
||||
...photo,
|
||||
caption: photo.caption ?? undefined
|
||||
}))}
|
||||
initialIndex={selectedPhotoIndex}
|
||||
isOpen={photoModalOpen}
|
||||
onClose={() => setPhotoModalOpen(false)}
|
||||
|
||||
@@ -134,7 +134,7 @@ export const QueueItem = memo(({
|
||||
<QueueItemHeader
|
||||
item={item}
|
||||
isMobile={isMobile}
|
||||
hasModeratorEdits={hasModeratorEdits}
|
||||
hasModeratorEdits={hasModeratorEdits ?? false}
|
||||
isLockedByOther={isLockedByOther}
|
||||
currentLockSubmissionId={currentLockSubmissionId}
|
||||
validationResult={validationResult}
|
||||
@@ -186,14 +186,14 @@ export const QueueItem = memo(({
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mt-3">
|
||||
<div className="text-sm font-medium mb-2">Attached Photos:</div>
|
||||
<PhotoGrid
|
||||
photos={reviewPhotos}
|
||||
onPhotoClick={onOpenPhotos}
|
||||
maxDisplay={isMobile ? 3 : 4}
|
||||
className="grid-cols-2 md:grid-cols-3"
|
||||
/>
|
||||
<div className="mt-3">
|
||||
<div className="text-sm font-medium mb-2">Attached Photos:</div>
|
||||
<PhotoGrid
|
||||
photos={reviewPhotos}
|
||||
onPhotoClick={(photos, index) => onOpenPhotos(photos as any, index)}
|
||||
maxDisplay={isMobile ? 3 : 4}
|
||||
className="grid-cols-2 md:grid-cols-3"
|
||||
/>
|
||||
{item.content.photos[0]?.caption && (
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
{item.content.photos[0].caption}
|
||||
@@ -231,7 +231,7 @@ export const QueueItem = memo(({
|
||||
<div className="text-sm space-y-2">
|
||||
<div>
|
||||
<span className="text-muted-foreground">Type:</span>{' '}
|
||||
<span className="font-medium">{getSubmissionTypeLabel(item.submission_type)}</span>
|
||||
<span className="font-medium">{getSubmissionTypeLabel(item.submission_type || 'unknown')}</span>
|
||||
</div>
|
||||
{item.submission_items && item.submission_items.length > 0 && (
|
||||
<div>
|
||||
|
||||
@@ -83,11 +83,11 @@ export const RecentActivity = forwardRef<RecentActivityRef>((props, ref) => {
|
||||
if (reviewsError) throw reviewsError;
|
||||
|
||||
// Get unique moderator IDs
|
||||
const moderatorIds = [
|
||||
...(submissions?.map(s => s.reviewer_id).filter(Boolean) || []),
|
||||
...(reports?.map(r => r.reviewed_by).filter(Boolean) || []),
|
||||
...(reviews?.map(r => r.moderated_by).filter(Boolean) || []),
|
||||
].filter((id, index, arr) => id && arr.indexOf(id) === index);
|
||||
const moderatorIds: string[] = [
|
||||
...(submissions?.map(s => s.reviewer_id).filter((id): id is string => id != null) || []),
|
||||
...(reports?.map(r => r.reviewed_by).filter((id): id is string => id != null) || []),
|
||||
...(reviews?.map(r => r.moderated_by).filter((id): id is string => id != null) || []),
|
||||
].filter((id, index, arr) => arr.indexOf(id) === index);
|
||||
|
||||
// Fetch moderator profiles
|
||||
const { data: profiles } = await supabase
|
||||
|
||||
@@ -61,7 +61,7 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({
|
||||
}
|
||||
|
||||
setItems((itemsData || []) as SubmissionItemData[]);
|
||||
setHasPhotos(photoData && photoData.length > 0);
|
||||
setHasPhotos(!!(photoData && photoData.length > 0));
|
||||
} catch (err) {
|
||||
logger.error('Failed to fetch submission items', { error: getErrorMessage(err) });
|
||||
setError('Failed to load submission details');
|
||||
|
||||
@@ -634,7 +634,10 @@ export function SubmissionReviewManager({
|
||||
<ConflictResolutionModal
|
||||
open={showConflictResolutionModal}
|
||||
onOpenChange={setShowConflictResolutionModal}
|
||||
conflictData={conflictData}
|
||||
conflictData={conflictData || {
|
||||
hasConflict: false,
|
||||
clientVersion: { last_modified_at: new Date().toISOString() }
|
||||
}}
|
||||
onResolve={async (strategy) => {
|
||||
if (strategy === 'keep-mine') {
|
||||
// Log conflict resolution
|
||||
|
||||
@@ -30,7 +30,7 @@ export const EntitySubmissionDisplay = memo(({ item, isMobile }: EntitySubmissio
|
||||
<div className="text-sm space-y-2">
|
||||
<div>
|
||||
<span className="text-muted-foreground">Type:</span>{' '}
|
||||
<span className="font-medium">{getSubmissionTypeLabel(item.submission_type)}</span>
|
||||
<span className="font-medium">{getSubmissionTypeLabel(item.submission_type || 'unknown')}</span>
|
||||
</div>
|
||||
{item.submission_items && item.submission_items.length > 0 && (
|
||||
<div>
|
||||
|
||||
@@ -54,7 +54,7 @@ export const PhotoSubmissionDisplay = memo(({
|
||||
title: photo.title,
|
||||
date_taken: photo.date_taken,
|
||||
}))}
|
||||
onPhotoClick={onOpenPhotos}
|
||||
onPhotoClick={(photos, index) => onOpenPhotos(photos as any, index)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -52,7 +52,7 @@ export const ReviewDisplay = memo(({ item, isMobile, onOpenPhotos }: ReviewDispl
|
||||
<div className="text-sm font-medium mb-2">Attached Photos:</div>
|
||||
<PhotoGrid
|
||||
photos={reviewPhotos}
|
||||
onPhotoClick={onOpenPhotos}
|
||||
onPhotoClick={(photos, index) => onOpenPhotos(photos as any, index)}
|
||||
maxDisplay={isMobile ? 3 : 4}
|
||||
className="grid-cols-2 md:grid-cols-3"
|
||||
/>
|
||||
|
||||
@@ -36,7 +36,7 @@ export function NotificationCenter() {
|
||||
>
|
||||
<Inbox
|
||||
applicationIdentifier={applicationIdentifier}
|
||||
subscriberId={subscriberId}
|
||||
subscriber={subscriberId || ''}
|
||||
appearance={appearance}
|
||||
onNotificationClick={handleNotificationClick}
|
||||
/>
|
||||
|
||||
@@ -30,7 +30,7 @@ const OperatorCard = ({ company }: OperatorCardProps) => {
|
||||
<div className="aspect-[3/2] relative bg-gradient-to-br from-primary/20 via-primary/10 to-transparent overflow-hidden">
|
||||
{(company.card_image_url || company.card_image_id) ? (
|
||||
<img
|
||||
src={company.card_image_url || getCloudflareImageUrl(company.card_image_id, 'card')}
|
||||
src={company.card_image_url || getCloudflareImageUrl(company.card_image_id || '', 'card')}
|
||||
srcSet={company.card_image_id ? `
|
||||
${getCloudflareImageUrl(company.card_image_id, 'cardthumb')} 600w,
|
||||
${getCloudflareImageUrl(company.card_image_id, 'card')} 1200w
|
||||
@@ -111,11 +111,11 @@ const OperatorCard = ({ company }: OperatorCardProps) => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{company.average_rating > 0 && (
|
||||
{company.average_rating != null && company.average_rating > 0 && (
|
||||
<div className="inline-flex items-center gap-1">
|
||||
<Star className="w-4 h-4 fill-yellow-500 text-yellow-500" />
|
||||
<span className="font-semibold">{company.average_rating.toFixed(1)}</span>
|
||||
{company.review_count > 0 && (
|
||||
{company.review_count != null && company.review_count > 0 && (
|
||||
<span className="text-muted-foreground">({company.review_count})</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,7 @@ const ParkOwnerCard = ({ company }: ParkOwnerCardProps) => {
|
||||
<div className="aspect-[3/2] relative bg-gradient-to-br from-primary/20 via-primary/10 to-transparent overflow-hidden">
|
||||
{(company.card_image_url || company.card_image_id) ? (
|
||||
<img
|
||||
src={company.card_image_url || getCloudflareImageUrl(company.card_image_id, 'card')}
|
||||
src={company.card_image_url || getCloudflareImageUrl(company.card_image_id || '', 'card')}
|
||||
srcSet={company.card_image_id ? `
|
||||
${getCloudflareImageUrl(company.card_image_id, 'cardthumb')} 600w,
|
||||
${getCloudflareImageUrl(company.card_image_id, 'card')} 1200w
|
||||
@@ -111,11 +111,11 @@ const ParkOwnerCard = ({ company }: ParkOwnerCardProps) => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{company.average_rating > 0 && (
|
||||
{company.average_rating != null && company.average_rating > 0 && (
|
||||
<div className="inline-flex items-center gap-1">
|
||||
<Star className="w-4 h-4 fill-yellow-500 text-yellow-500" />
|
||||
<span className="font-semibold">{company.average_rating.toFixed(1)}</span>
|
||||
{company.review_count > 0 && (
|
||||
{company.review_count != null && company.review_count > 0 && (
|
||||
<span className="text-muted-foreground">({company.review_count})</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user