mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:51:13 -05:00
Refactor: Implement complete plan
This commit is contained in:
@@ -121,7 +121,7 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
|
||||
const { data, error } = await query;
|
||||
if (error) throw error;
|
||||
|
||||
let processedData = (data || []) as any[];
|
||||
let processedData = data || [];
|
||||
|
||||
// Sort by name client-side if needed
|
||||
if (sortBy === 'name') {
|
||||
|
||||
@@ -170,7 +170,7 @@ export function useEntityCache() {
|
||||
|
||||
// Collect all entity IDs from submissions
|
||||
submissions.forEach(submission => {
|
||||
const content = submission.content as any;
|
||||
const content = submission.content;
|
||||
if (content && typeof content === 'object') {
|
||||
if (content.ride_id) rideIds.add(content.ride_id);
|
||||
if (content.park_id) parkIds.add(content.park_id);
|
||||
|
||||
44
src/hooks/useCoasterStats.ts
Normal file
44
src/hooks/useCoasterStats.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
|
||||
export interface CoasterStat {
|
||||
id: string;
|
||||
ride_id: string;
|
||||
stat_name: string;
|
||||
stat_value: number;
|
||||
unit?: string | null;
|
||||
category?: string | null;
|
||||
description?: string | null;
|
||||
display_order: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export function useCoasterStats(rideId: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: ['coaster-stats', rideId],
|
||||
queryFn: async () => {
|
||||
if (!rideId) return [];
|
||||
|
||||
const { data, error } = await (supabase as any)
|
||||
.from('ride_coaster_stats')
|
||||
.select('*')
|
||||
.eq('ride_id', rideId)
|
||||
.order('display_order');
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return (data || []).map((stat: any) => ({
|
||||
id: stat.id,
|
||||
ride_id: stat.ride_id,
|
||||
stat_name: stat.stat_name,
|
||||
stat_value: stat.stat_value,
|
||||
unit: stat.unit || null,
|
||||
category: stat.category || null,
|
||||
description: stat.description || null,
|
||||
display_order: stat.display_order,
|
||||
created_at: stat.created_at,
|
||||
})) as CoasterStat[];
|
||||
},
|
||||
enabled: !!rideId
|
||||
});
|
||||
}
|
||||
@@ -41,8 +41,8 @@ export function useEntityVersions(entityType: EntityType, entityId: string) {
|
||||
const versionTable = `${entityType}_versions`;
|
||||
const entityIdCol = `${entityType}_id`;
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from(versionTable as any)
|
||||
const { data, error } = await (supabase as any)
|
||||
.from(versionTable)
|
||||
.select(`
|
||||
*,
|
||||
profiles:created_by(username, display_name, avatar_url)
|
||||
@@ -63,7 +63,7 @@ export function useEntityVersions(entityType: EntityType, entityId: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
const versionsWithProfiles = (data as any[]).map((v: any) => ({
|
||||
const versionsWithProfiles = (data || []).map((v: any) => ({
|
||||
...v,
|
||||
profiles: v.profiles || {
|
||||
username: 'Unknown',
|
||||
|
||||
52
src/hooks/useTechnicalSpecifications.ts
Normal file
52
src/hooks/useTechnicalSpecifications.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
|
||||
export interface TechnicalSpecification {
|
||||
id: string;
|
||||
entity_type: 'ride' | 'ride_model';
|
||||
entity_id: string;
|
||||
spec_name: string;
|
||||
spec_value: string;
|
||||
spec_unit?: string | null;
|
||||
category?: string | null;
|
||||
display_order: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export function useTechnicalSpecifications(
|
||||
entityType: 'ride' | 'ride_model',
|
||||
entityId: string | undefined
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: ['technical-specifications', entityType, entityId],
|
||||
queryFn: async () => {
|
||||
if (!entityId) return [];
|
||||
|
||||
const tableName = entityType === 'ride'
|
||||
? 'ride_technical_specifications'
|
||||
: 'ride_model_technical_specifications';
|
||||
const idColumn = entityType === 'ride' ? 'ride_id' : 'ride_model_id';
|
||||
|
||||
const { data, error } = await (supabase as any)
|
||||
.from(tableName)
|
||||
.select('*')
|
||||
.eq(idColumn, entityId)
|
||||
.order('display_order');
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return (data || []).map((spec: any) => ({
|
||||
id: spec.id,
|
||||
entity_type: entityType,
|
||||
entity_id: entityId,
|
||||
spec_name: spec.spec_name,
|
||||
spec_value: spec.spec_value,
|
||||
spec_unit: spec.spec_unit || null,
|
||||
category: spec.category || null,
|
||||
display_order: spec.display_order,
|
||||
created_at: spec.created_at,
|
||||
})) as TechnicalSpecification[];
|
||||
},
|
||||
enabled: !!entityId
|
||||
});
|
||||
}
|
||||
@@ -121,7 +121,7 @@ export const rideModelRuntimeSchema = z.object({
|
||||
manufacturer_id: z.string().uuid().nullable().optional(),
|
||||
category: z.string(),
|
||||
description: z.string().nullable().optional(),
|
||||
technical_specs: z.array(z.unknown()).nullable().optional(),
|
||||
// Note: technical_specs deprecated - use ride_model_technical_specifications table
|
||||
created_at: z.string().optional(),
|
||||
updated_at: z.string().optional(),
|
||||
}).passthrough();
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Dialog, DialogContent } from '@/components/ui/dialog';
|
||||
import { ArrowLeft, FerrisWheel, Building2, Edit } from 'lucide-react';
|
||||
import { RideModel, Ride, Company } from '@/types/database';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { useTechnicalSpecifications } from '@/hooks/useTechnicalSpecifications';
|
||||
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
@@ -30,6 +31,9 @@ export default function RideModelDetail() {
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||
const [statistics, setStatistics] = useState({ rideCount: 0, photoCount: 0 });
|
||||
|
||||
// Fetch technical specifications from relational table
|
||||
const { data: technicalSpecs } = useTechnicalSpecifications('ride_model', model?.id);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
// Fetch manufacturer
|
||||
@@ -266,15 +270,19 @@ export default function RideModelDetail() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{model.technical_specs && typeof model.technical_specs === 'object' && Object.keys(model.technical_specs).length > 0 && (
|
||||
{technicalSpecs && technicalSpecs.length > 0 && (
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<h2 className="text-2xl font-semibold mb-4">Technical Specifications</h2>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{Object.entries(model.technical_specs as Record<string, any>).map(([key, value]) => (
|
||||
<div key={key} className="flex justify-between py-2 border-b">
|
||||
<span className="font-medium capitalize">{key.replace(/_/g, ' ')}</span>
|
||||
<span className="text-muted-foreground">{String(value)}</span>
|
||||
{technicalSpecs.map((spec) => (
|
||||
<div key={spec.id} className="flex justify-between py-2 border-b">
|
||||
<span className="font-medium capitalize">
|
||||
{spec.spec_name.replace(/_/g, ' ')}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
{spec.spec_value} {spec.spec_unit || ''}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -118,8 +118,8 @@ export interface RideModel {
|
||||
category: 'roller_coaster' | 'flat_ride' | 'water_ride' | 'dark_ride' | 'kiddie_ride' | 'transportation';
|
||||
ride_type?: string;
|
||||
description?: string;
|
||||
technical_specs?: Record<string, any>;
|
||||
technical_specifications?: RideModelTechnicalSpec[];
|
||||
// Note: technical_specs deprecated - use ride_model_technical_specifications table
|
||||
// Load via useTechnicalSpecifications hook instead
|
||||
banner_image_url?: string;
|
||||
banner_image_id?: string;
|
||||
card_image_url?: string;
|
||||
|
||||
@@ -127,7 +127,7 @@ export interface RideModelVersion extends BaseVersionWithProfile {
|
||||
manufacturer_id: string | null;
|
||||
category: string;
|
||||
description: string | null;
|
||||
technical_specs: Record<string, any> | null;
|
||||
// Note: technical_specs removed - use ride_model_technical_specifications table
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user