mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 13:51:14 -05:00
Refactor: Implement complete plan
This commit is contained in:
@@ -19,7 +19,7 @@ import { UserListManager } from '@/components/lists/UserListManager';
|
||||
import { RideCreditsManager } from '@/components/profile/RideCreditsManager';
|
||||
import { useUsernameValidation } from '@/hooks/useUsernameValidation';
|
||||
import { User, MapPin, Calendar, Star, Trophy, Settings, Camera, Edit3, Save, X, ArrowLeft, Check, AlertCircle, Loader2, UserX, FileText, Image } from 'lucide-react';
|
||||
import { Profile as ProfileType, ActivityEntry, ReviewActivity, SubmissionActivity, RankingActivity } from '@/types/database';
|
||||
import { Profile as ProfileType } from '@/types/database';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
@@ -30,6 +30,95 @@ import { UserBlockButton } from '@/components/profile/UserBlockButton';
|
||||
import { PersonalLocationDisplay } from '@/components/profile/PersonalLocationDisplay';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
|
||||
// Activity type definitions
|
||||
interface SubmissionActivity {
|
||||
id: string;
|
||||
type: 'submission';
|
||||
submission_type: 'park' | 'ride' | 'photo' | 'company' | 'ride_model';
|
||||
status: string;
|
||||
created_at: string;
|
||||
content?: {
|
||||
action?: 'edit' | 'create';
|
||||
name?: string;
|
||||
slug?: string;
|
||||
entity_slug?: string;
|
||||
entity_name?: string;
|
||||
park_slug?: string;
|
||||
park_name?: string;
|
||||
company_type?: string;
|
||||
manufacturer_slug?: string;
|
||||
description?: string;
|
||||
};
|
||||
photo_preview?: string;
|
||||
photo_count?: number;
|
||||
entity_type?: 'park' | 'ride' | 'company';
|
||||
entity_id?: string;
|
||||
}
|
||||
|
||||
interface RankingActivity {
|
||||
id: string;
|
||||
type: 'ranking';
|
||||
title: string;
|
||||
description?: string;
|
||||
list_type: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface ReviewActivity {
|
||||
id: string;
|
||||
type: 'review';
|
||||
rating: number;
|
||||
title?: string;
|
||||
content?: string;
|
||||
created_at: string;
|
||||
moderation_status?: string;
|
||||
park_id?: string;
|
||||
ride_id?: string;
|
||||
parks?: {
|
||||
name: string;
|
||||
slug: string;
|
||||
} | null;
|
||||
rides?: {
|
||||
name: string;
|
||||
slug: string;
|
||||
parks?: {
|
||||
name: string;
|
||||
slug: string;
|
||||
} | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
interface CreditActivity {
|
||||
id: string;
|
||||
type: 'credit';
|
||||
ride_count: number;
|
||||
first_ride_date?: string;
|
||||
created_at: string;
|
||||
rides?: {
|
||||
name: string;
|
||||
slug: string;
|
||||
parks?: {
|
||||
name: string;
|
||||
slug: string;
|
||||
} | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
type ActivityEntry = SubmissionActivity | RankingActivity | ReviewActivity | CreditActivity;
|
||||
|
||||
// Type guards
|
||||
const isSubmissionActivity = (act: ActivityEntry): act is SubmissionActivity =>
|
||||
act.type === 'submission';
|
||||
|
||||
const isRankingActivity = (act: ActivityEntry): act is RankingActivity =>
|
||||
act.type === 'ranking';
|
||||
|
||||
const isReviewActivity = (act: ActivityEntry): act is ReviewActivity =>
|
||||
act.type === 'review';
|
||||
|
||||
const isCreditActivity = (act: ActivityEntry): act is CreditActivity =>
|
||||
act.type === 'credit';
|
||||
|
||||
export default function Profile() {
|
||||
const {
|
||||
username
|
||||
@@ -764,7 +853,7 @@ export default function Profile() {
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<p className="font-medium">
|
||||
{reviewActivity.title || reviewActivity.description || 'Left a review'}
|
||||
{reviewActivity.title || reviewActivity.content || 'Left a review'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 mb-2">
|
||||
@@ -789,58 +878,58 @@ export default function Profile() {
|
||||
</>
|
||||
);
|
||||
})()
|
||||
) : activity.type === 'submission' ? (
|
||||
) : isSubmissionActivity(activity) ? (
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<p className="font-medium">
|
||||
{(activity as any).content?.action === 'edit' ? 'Edited' : 'Submitted'}{' '}
|
||||
{(activity as any).submission_type === 'photo' ? 'photos for' : (activity as any).submission_type || 'content'}
|
||||
{(activity as any).content?.name && ` ${(activity as any).content.name}`}
|
||||
{activity.content?.action === 'edit' ? 'Edited' : 'Submitted'}{' '}
|
||||
{activity.submission_type === 'photo' ? 'photos for' : activity.submission_type || 'content'}
|
||||
{activity.content?.name && ` ${activity.content.name}`}
|
||||
</p>
|
||||
{(activity as any).status === 'pending' && (
|
||||
{activity.status === 'pending' && (
|
||||
<Badge variant="secondary" className="text-xs">Pending</Badge>
|
||||
)}
|
||||
{(activity as any).status === 'approved' && (
|
||||
{activity.status === 'approved' && (
|
||||
<Badge variant="default" className="text-xs">Approved</Badge>
|
||||
)}
|
||||
{(activity as any).status === 'rejected' && (
|
||||
{activity.status === 'rejected' && (
|
||||
<Badge variant="destructive" className="text-xs">Rejected</Badge>
|
||||
)}
|
||||
{(activity as any).status === 'partially_approved' && (
|
||||
{activity.status === 'partially_approved' && (
|
||||
<Badge variant="outline" className="text-xs">Partially Approved</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Photo preview for photo submissions */}
|
||||
{(activity as any).submission_type === 'photo' && (activity as any).photo_preview && (
|
||||
{activity.submission_type === 'photo' && activity.photo_preview && (
|
||||
<div className="flex gap-2 items-center mb-2">
|
||||
<img
|
||||
src={(activity as any).photo_preview}
|
||||
src={activity.photo_preview}
|
||||
alt="Photo preview"
|
||||
className="w-16 h-16 rounded object-cover border"
|
||||
/>
|
||||
{(activity as any).photo_count > 1 && (
|
||||
{activity.photo_count && activity.photo_count > 1 && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
+{(activity as any).photo_count - 1} more
|
||||
+{activity.photo_count - 1} more
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Entity link for photo submissions */}
|
||||
{(activity as any).submission_type === 'photo' && (activity as any).content?.entity_slug && (
|
||||
{activity.submission_type === 'photo' && activity.content?.entity_slug && (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{(activity as any).entity_type === 'park' ? (
|
||||
<Link to={`/parks/${(activity as any).content.entity_slug}`} className="hover:text-accent transition-colors">
|
||||
{(activity as any).content.entity_name || 'View park'}
|
||||
{activity.entity_type === 'park' ? (
|
||||
<Link to={`/parks/${activity.content.entity_slug}`} className="hover:text-accent transition-colors">
|
||||
{activity.content.entity_name || 'View park'}
|
||||
</Link>
|
||||
) : (activity as any).entity_type === 'ride' ? (
|
||||
) : activity.entity_type === 'ride' ? (
|
||||
<>
|
||||
<Link to={`/parks/${(activity as any).content.park_slug}/rides/${(activity as any).content.entity_slug}`} className="hover:text-accent transition-colors">
|
||||
{(activity as any).content.entity_name || 'View ride'}
|
||||
<Link to={`/parks/${activity.content.park_slug}/rides/${activity.content.entity_slug}`} className="hover:text-accent transition-colors">
|
||||
{activity.content.entity_name || 'View ride'}
|
||||
</Link>
|
||||
{(activity as any).content.park_name && (
|
||||
<span className="text-muted-foreground/70"> at {(activity as any).content.park_name}</span>
|
||||
{activity.content.park_name && (
|
||||
<span className="text-muted-foreground/70"> at {activity.content.park_name}</span>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
@@ -848,42 +937,42 @@ export default function Profile() {
|
||||
)}
|
||||
|
||||
{/* Links for entity submissions */}
|
||||
{(activity as any).status === 'approved' && (activity as any).submission_type !== 'photo' && (
|
||||
{activity.status === 'approved' && activity.submission_type !== 'photo' && (
|
||||
<>
|
||||
{(activity as any).submission_type === 'park' && (activity as any).content?.slug && (
|
||||
{activity.submission_type === 'park' && activity.content?.slug && (
|
||||
<Link
|
||||
to={`/parks/${(activity as any).content.slug}`}
|
||||
to={`/parks/${activity.content.slug}`}
|
||||
className="text-sm text-accent hover:underline"
|
||||
>
|
||||
View park →
|
||||
</Link>
|
||||
)}
|
||||
{(activity as any).submission_type === 'ride' && (activity as any).content?.slug && (activity as any).content?.park_slug && (
|
||||
{activity.submission_type === 'ride' && activity.content?.slug && activity.content?.park_slug && (
|
||||
<div className="text-sm">
|
||||
<Link
|
||||
to={`/parks/${(activity as any).content.park_slug}/rides/${(activity as any).content.slug}`}
|
||||
to={`/parks/${activity.content.park_slug}/rides/${activity.content.slug}`}
|
||||
className="text-accent hover:underline"
|
||||
>
|
||||
View ride →
|
||||
</Link>
|
||||
{(activity as any).content.park_name && (
|
||||
{activity.content.park_name && (
|
||||
<span className="text-muted-foreground ml-1">
|
||||
at {(activity as any).content.park_name}
|
||||
at {activity.content.park_name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{(activity as any).submission_type === 'company' && (activity as any).content?.slug && (
|
||||
{activity.submission_type === 'company' && activity.content?.slug && (
|
||||
<Link
|
||||
to={`/${(activity as any).content.company_type === 'operator' ? 'operators' : (activity as any).content.company_type === 'property_owner' ? 'owners' : (activity as any).content.company_type === 'manufacturer' ? 'manufacturers' : 'designers'}/${(activity as any).content.slug}`}
|
||||
to={`/${activity.content.company_type === 'operator' ? 'operators' : activity.content.company_type === 'property_owner' ? 'owners' : activity.content.company_type === 'manufacturer' ? 'manufacturers' : 'designers'}/${activity.content.slug}`}
|
||||
className="text-sm text-accent hover:underline"
|
||||
>
|
||||
View {(activity as any).content.company_type || 'company'} →
|
||||
View {activity.content.company_type || 'company'} →
|
||||
</Link>
|
||||
)}
|
||||
{(activity as any).submission_type === 'ride_model' && (activity as any).content?.slug && (activity as any).content?.manufacturer_slug && (
|
||||
{activity.submission_type === 'ride_model' && activity.content?.slug && activity.content?.manufacturer_slug && (
|
||||
<Link
|
||||
to={`/manufacturers/${(activity as any).content.manufacturer_slug}/models/${(activity as any).content.slug}`}
|
||||
to={`/manufacturers/${activity.content.manufacturer_slug}/models/${activity.content.slug}`}
|
||||
className="text-sm text-accent hover:underline"
|
||||
>
|
||||
View model →
|
||||
@@ -892,54 +981,51 @@ export default function Profile() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{(activity as any).content?.description && (activity as any).submission_type !== 'photo' && (
|
||||
{activity.content?.description && activity.submission_type !== 'photo' && (
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
{(activity as any).content.description}
|
||||
{activity.content.description}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
) : activity.type === 'ranking' ? (
|
||||
) : isRankingActivity(activity) ? (
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Link
|
||||
to={`/profile/${profile?.username}/lists`}
|
||||
className="font-medium hover:text-accent transition-colors"
|
||||
>
|
||||
Created ranking: {(activity as any).title || 'Untitled'}
|
||||
Created ranking: {activity.title || 'Untitled'}
|
||||
</Link>
|
||||
<Badge variant="outline" className="text-xs capitalize">
|
||||
{((activity as any).list_type || '').replace('_', ' ')}
|
||||
{(activity.list_type || '').replace('_', ' ')}
|
||||
</Badge>
|
||||
{(activity as any).is_public === false && (
|
||||
<Badge variant="secondary" className="text-xs">Private</Badge>
|
||||
)}
|
||||
</div>
|
||||
{(activity as any).description && (
|
||||
{activity.description && (
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
{(activity as any).description}
|
||||
{activity.description}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
) : isCreditActivity(activity) ? (
|
||||
<>
|
||||
<p className="font-medium mb-1">Added ride credit</p>
|
||||
{(activity as any).rides && (
|
||||
{activity.rides && (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<Link to={`/parks/${(activity as any).rides.parks?.slug}/rides/${(activity as any).rides.slug}`} className="hover:text-accent transition-colors">
|
||||
{(activity as any).rides.name}
|
||||
<Link to={`/parks/${activity.rides.parks?.slug}/rides/${activity.rides.slug}`} className="hover:text-accent transition-colors">
|
||||
{activity.rides.name}
|
||||
</Link>
|
||||
{(activity as any).rides.parks && (
|
||||
<span className="text-muted-foreground/70"> at {(activity as any).rides.parks.name}</span>
|
||||
{activity.rides.parks && (
|
||||
<span className="text-muted-foreground/70"> at {activity.rides.parks.name}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{(activity as any).ride_count > 1 && (
|
||||
{activity.ride_count > 1 && (
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Ridden {(activity as any).ride_count} times
|
||||
Ridden {activity.ride_count} times
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 text-xs text-muted-foreground">
|
||||
|
||||
Reference in New Issue
Block a user