mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-28 15:47:01 -05:00
Compare commits
4 Commits
d00ea2a3ee
...
e631ecc2b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e631ecc2b1 | ||
|
|
57ac5c1f1a | ||
|
|
b189f40c1f | ||
|
|
328a77a0a8 |
@@ -220,10 +220,12 @@ function injectOGTags(html: string, ogTags: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function handler(req: VercelRequest, res: VercelResponse): Promise<void> {
|
export default async function handler(req: VercelRequest, res: VercelResponse): Promise<void> {
|
||||||
|
let pathname = '/';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const userAgent = req.headers['user-agent'] || '';
|
const userAgent = req.headers['user-agent'] || '';
|
||||||
const fullUrl = `https://${req.headers.host}${req.url}`;
|
const fullUrl = `https://${req.headers.host}${req.url}`;
|
||||||
const pathname = new URL(fullUrl).pathname;
|
pathname = new URL(fullUrl).pathname;
|
||||||
|
|
||||||
// Comprehensive bot detection with headers
|
// Comprehensive bot detection with headers
|
||||||
const botDetection = detectBot(userAgent, req.headers as Record<string, string | string[] | undefined>);
|
const botDetection = detectBot(userAgent, req.headers as Record<string, string | string[] | undefined>);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Building, MapPin, Calendar, Globe, ExternalLink, AlertCircle } from 'lucide-react';
|
import { Building, MapPin, Calendar, Globe, ExternalLink, AlertCircle } from 'lucide-react';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import { FlexibleDateDisplay } from '@/components/ui/flexible-date-display';
|
||||||
|
import type { DatePrecision } from '@/components/ui/flexible-date-input';
|
||||||
import type { CompanySubmissionData } from '@/types/submission-data';
|
import type { CompanySubmissionData } from '@/types/submission-data';
|
||||||
|
|
||||||
interface RichCompanyDisplayProps {
|
interface RichCompanyDisplayProps {
|
||||||
@@ -63,12 +65,11 @@ export function RichCompanyDisplay({ data, actionType, showAllFields = true }: R
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-sm ml-6">
|
<div className="text-sm ml-6">
|
||||||
{data.founded_date ? (
|
{data.founded_date ? (
|
||||||
<>
|
<FlexibleDateDisplay
|
||||||
<span className="font-medium">{new Date(data.founded_date).toLocaleDateString()}</span>
|
date={data.founded_date}
|
||||||
{data.founded_date_precision && data.founded_date_precision !== 'day' && (
|
precision={(data.founded_date_precision as DatePrecision) || 'day'}
|
||||||
<span className="text-xs text-muted-foreground ml-1">({data.founded_date_precision})</span>
|
className="font-medium"
|
||||||
)}
|
/>
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<span className="font-medium">{data.founded_year}</span>
|
<span className="font-medium">{data.founded_year}</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Building2, MapPin, Calendar, Globe, ExternalLink, Users, AlertCircle } from 'lucide-react';
|
import { Building2, MapPin, Calendar, Globe, ExternalLink, Users, AlertCircle } from 'lucide-react';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import { FlexibleDateDisplay } from '@/components/ui/flexible-date-display';
|
||||||
|
import type { DatePrecision } from '@/components/ui/flexible-date-input';
|
||||||
import type { ParkSubmissionData } from '@/types/submission-data';
|
import type { ParkSubmissionData } from '@/types/submission-data';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { supabase } from '@/lib/supabaseClient';
|
import { supabase } from '@/lib/supabaseClient';
|
||||||
@@ -154,19 +156,21 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich
|
|||||||
{data.opening_date && (
|
{data.opening_date && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Opened:</span>{' '}
|
<span className="text-muted-foreground">Opened:</span>{' '}
|
||||||
<span className="font-medium">{new Date(data.opening_date).toLocaleDateString()}</span>
|
<FlexibleDateDisplay
|
||||||
{data.opening_date_precision && data.opening_date_precision !== 'day' && (
|
date={data.opening_date}
|
||||||
<span className="text-xs text-muted-foreground ml-1">({data.opening_date_precision})</span>
|
precision={(data.opening_date_precision as DatePrecision) || 'day'}
|
||||||
)}
|
className="font-medium"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{data.closing_date && (
|
{data.closing_date && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Closed:</span>{' '}
|
<span className="text-muted-foreground">Closed:</span>{' '}
|
||||||
<span className="font-medium">{new Date(data.closing_date).toLocaleDateString()}</span>
|
<FlexibleDateDisplay
|
||||||
{data.closing_date_precision && data.closing_date_precision !== 'day' && (
|
date={data.closing_date}
|
||||||
<span className="text-xs text-muted-foreground ml-1">({data.closing_date_precision})</span>
|
precision={(data.closing_date_precision as DatePrecision) || 'day'}
|
||||||
)}
|
className="font-medium"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Train, Gauge, Ruler, Zap, Calendar, Building, User, ExternalLink, AlertCircle, TrendingUp, Droplets, Sparkles, RotateCw, Baby, Navigation } from 'lucide-react';
|
import { Train, Gauge, Ruler, Zap, Calendar, Building, User, ExternalLink, AlertCircle, TrendingUp, Droplets, Sparkles, RotateCw, Baby, Navigation } from 'lucide-react';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import { FlexibleDateDisplay } from '@/components/ui/flexible-date-display';
|
||||||
|
import type { DatePrecision } from '@/components/ui/flexible-date-input';
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||||
import type { RideSubmissionData } from '@/types/submission-data';
|
import type { RideSubmissionData } from '@/types/submission-data';
|
||||||
@@ -602,19 +604,21 @@ export function RichRideDisplay({ data, actionType, showAllFields = true }: Rich
|
|||||||
{data.opening_date && (
|
{data.opening_date && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Opened:</span>{' '}
|
<span className="text-muted-foreground">Opened:</span>{' '}
|
||||||
<span className="font-medium">{new Date(data.opening_date).toLocaleDateString()}</span>
|
<FlexibleDateDisplay
|
||||||
{data.opening_date_precision && data.opening_date_precision !== 'day' && (
|
date={data.opening_date}
|
||||||
<span className="text-xs text-muted-foreground ml-1">({data.opening_date_precision})</span>
|
precision={(data.opening_date_precision as DatePrecision) || 'day'}
|
||||||
)}
|
className="font-medium"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{data.closing_date && (
|
{data.closing_date && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Closed:</span>{' '}
|
<span className="text-muted-foreground">Closed:</span>{' '}
|
||||||
<span className="font-medium">{new Date(data.closing_date).toLocaleDateString()}</span>
|
<FlexibleDateDisplay
|
||||||
{data.closing_date_precision && data.closing_date_precision !== 'day' && (
|
date={data.closing_date}
|
||||||
<span className="text-xs text-muted-foreground ml-1">({data.closing_date_precision})</span>
|
precision={(data.closing_date_precision as DatePrecision) || 'day'}
|
||||||
)}
|
className="font-medium"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -676,9 +676,6 @@ export async function submitParkCreation(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'park',
|
submission_type: 'park',
|
||||||
content: {
|
|
||||||
action: 'create'
|
|
||||||
},
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select('id')
|
.select('id')
|
||||||
@@ -873,10 +870,6 @@ export async function submitParkUpdate(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'park',
|
submission_type: 'park',
|
||||||
content: {
|
|
||||||
action: 'edit',
|
|
||||||
park_id: parkId
|
|
||||||
},
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select('id')
|
.select('id')
|
||||||
@@ -1132,9 +1125,6 @@ export async function submitRideCreation(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'ride',
|
submission_type: 'ride',
|
||||||
content: {
|
|
||||||
action: 'create'
|
|
||||||
},
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select('id')
|
.select('id')
|
||||||
@@ -1334,10 +1324,6 @@ export async function submitRideUpdate(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'ride',
|
submission_type: 'ride',
|
||||||
content: {
|
|
||||||
action: 'edit',
|
|
||||||
ride_id: rideId
|
|
||||||
},
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
@@ -1477,9 +1463,6 @@ export async function submitRideModelCreation(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'ride_model',
|
submission_type: 'ride_model',
|
||||||
content: {
|
|
||||||
action: 'create'
|
|
||||||
},
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
@@ -1556,10 +1539,6 @@ export async function submitRideModelUpdate(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'ride_model',
|
submission_type: 'ride_model',
|
||||||
content: {
|
|
||||||
action: 'edit',
|
|
||||||
ride_model_id: rideModelId
|
|
||||||
},
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
@@ -1619,7 +1598,6 @@ export async function submitManufacturerCreation(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'manufacturer',
|
submission_type: 'manufacturer',
|
||||||
content: { action: 'create' },
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
@@ -1677,7 +1655,6 @@ export async function submitManufacturerUpdate(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'manufacturer',
|
submission_type: 'manufacturer',
|
||||||
content: { action: 'edit', company_id: companyId },
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
@@ -1732,7 +1709,6 @@ export async function submitDesignerCreation(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'designer',
|
submission_type: 'designer',
|
||||||
content: { action: 'create' },
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
@@ -1790,7 +1766,6 @@ export async function submitDesignerUpdate(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'designer',
|
submission_type: 'designer',
|
||||||
content: { action: 'edit', company_id: companyId },
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
@@ -1845,7 +1820,6 @@ export async function submitOperatorCreation(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'operator',
|
submission_type: 'operator',
|
||||||
content: { action: 'create' },
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
@@ -1903,7 +1877,6 @@ export async function submitOperatorUpdate(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'operator',
|
submission_type: 'operator',
|
||||||
content: { action: 'edit', company_id: companyId },
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
@@ -1958,7 +1931,6 @@ export async function submitPropertyOwnerCreation(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'property_owner',
|
submission_type: 'property_owner',
|
||||||
content: { action: 'create' },
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
@@ -2016,7 +1988,6 @@ export async function submitPropertyOwnerUpdate(
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
submission_type: 'property_owner',
|
submission_type: 'property_owner',
|
||||||
content: { action: 'edit', company_id: companyId },
|
|
||||||
status: 'pending' as const
|
status: 'pending' as const
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
|
|||||||
@@ -644,7 +644,21 @@ export default function ParkDetail() {
|
|||||||
park_type: park?.park_type,
|
park_type: park?.park_type,
|
||||||
status: park?.status,
|
status: park?.status,
|
||||||
opening_date: park?.opening_date ?? undefined,
|
opening_date: park?.opening_date ?? undefined,
|
||||||
|
opening_date_precision: (park?.opening_date_precision as 'day' | 'month' | 'year') ?? undefined,
|
||||||
closing_date: park?.closing_date ?? undefined,
|
closing_date: park?.closing_date ?? undefined,
|
||||||
|
closing_date_precision: (park?.closing_date_precision as 'day' | 'month' | 'year') ?? undefined,
|
||||||
|
location_id: park?.location?.id,
|
||||||
|
location: park?.location ? {
|
||||||
|
name: park.location.name || '',
|
||||||
|
city: park.location.city || '',
|
||||||
|
state_province: park.location.state_province || '',
|
||||||
|
country: park.location.country || '',
|
||||||
|
postal_code: park.location.postal_code || '',
|
||||||
|
latitude: park.location.latitude || 0,
|
||||||
|
longitude: park.location.longitude || 0,
|
||||||
|
timezone: park.location.timezone || '',
|
||||||
|
display_name: park.location.name || '',
|
||||||
|
} : undefined,
|
||||||
website_url: park?.website_url ?? undefined,
|
website_url: park?.website_url ?? undefined,
|
||||||
phone: park?.phone ?? undefined,
|
phone: park?.phone ?? undefined,
|
||||||
email: park?.email ?? undefined,
|
email: park?.email ?? undefined,
|
||||||
|
|||||||
@@ -406,7 +406,8 @@ interface ApprovalRequest {
|
|||||||
const RIDE_FIELDS = [
|
const RIDE_FIELDS = [
|
||||||
'name', 'slug', 'description', 'park_id', 'ride_model_id',
|
'name', 'slug', 'description', 'park_id', 'ride_model_id',
|
||||||
'manufacturer_id', 'designer_id', 'category', 'status',
|
'manufacturer_id', 'designer_id', 'category', 'status',
|
||||||
'opening_date', 'closing_date', 'height_requirement', 'age_requirement',
|
'opening_date', 'opening_date_precision', 'closing_date', 'closing_date_precision',
|
||||||
|
'height_requirement', 'age_requirement',
|
||||||
'capacity_per_hour', 'duration_seconds', 'max_speed_kmh',
|
'capacity_per_hour', 'duration_seconds', 'max_speed_kmh',
|
||||||
'max_height_meters', 'length_meters', 'inversions',
|
'max_height_meters', 'length_meters', 'inversions',
|
||||||
'ride_sub_type', 'coaster_type', 'seating_type', 'intensity_level',
|
'ride_sub_type', 'coaster_type', 'seating_type', 'intensity_level',
|
||||||
@@ -416,8 +417,8 @@ const RIDE_FIELDS = [
|
|||||||
|
|
||||||
const PARK_FIELDS = [
|
const PARK_FIELDS = [
|
||||||
'name', 'slug', 'description', 'park_type', 'status',
|
'name', 'slug', 'description', 'park_type', 'status',
|
||||||
'opening_date', 'closing_date', 'location_id', 'operator_id',
|
'opening_date', 'opening_date_precision', 'closing_date', 'closing_date_precision',
|
||||||
'property_owner_id', 'website_url', 'phone', 'email',
|
'location_id', 'operator_id', 'property_owner_id', 'website_url', 'phone', 'email',
|
||||||
'banner_image_url', 'banner_image_id', 'card_image_url', 'card_image_id'
|
'banner_image_url', 'banner_image_id', 'card_image_url', 'card_image_id'
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1519,6 +1520,27 @@ function normalizeStatusValue(data: any): any {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeParkTypeValue(data: any): any {
|
||||||
|
if (data.park_type) {
|
||||||
|
// Map display values to database values
|
||||||
|
const parkTypeMap: Record<string, string> = {
|
||||||
|
// Display names
|
||||||
|
'Theme Park': 'theme_park',
|
||||||
|
'Amusement Park': 'amusement_park',
|
||||||
|
'Water Park': 'water_park',
|
||||||
|
'Family Entertainment': 'family_entertainment',
|
||||||
|
// Already lowercase values (for new submissions)
|
||||||
|
'theme_park': 'theme_park',
|
||||||
|
'amusement_park': 'amusement_park',
|
||||||
|
'water_park': 'water_park',
|
||||||
|
'family_entertainment': 'family_entertainment'
|
||||||
|
};
|
||||||
|
|
||||||
|
data.park_type = parkTypeMap[data.park_type] || data.park_type;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
async function createPark(supabase: any, data: any): Promise<string> {
|
async function createPark(supabase: any, data: any): Promise<string> {
|
||||||
const submitterId = data._submitter_id;
|
const submitterId = data._submitter_id;
|
||||||
let uploadedPhotos: any[] = [];
|
let uploadedPhotos: any[] = [];
|
||||||
@@ -1597,7 +1619,7 @@ async function createPark(supabase: any, data: any): Promise<string> {
|
|||||||
parkId = data.park_id;
|
parkId = data.park_id;
|
||||||
delete data.park_id; // Remove ID from update data
|
delete data.park_id; // Remove ID from update data
|
||||||
|
|
||||||
const normalizedData = normalizeStatusValue(data);
|
const normalizedData = normalizeParkTypeValue(normalizeStatusValue(data));
|
||||||
const sanitizedData = sanitizeDateFields(normalizedData);
|
const sanitizedData = sanitizeDateFields(normalizedData);
|
||||||
const filteredData = filterDatabaseFields(sanitizedData, PARK_FIELDS);
|
const filteredData = filterDatabaseFields(sanitizedData, PARK_FIELDS);
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
@@ -1608,7 +1630,7 @@ async function createPark(supabase: any, data: any): Promise<string> {
|
|||||||
if (error) throw new Error(`Failed to update park: ${error.message}`);
|
if (error) throw new Error(`Failed to update park: ${error.message}`);
|
||||||
} else {
|
} else {
|
||||||
edgeLogger.info('Creating new park', { action: 'approval_create_park' });
|
edgeLogger.info('Creating new park', { action: 'approval_create_park' });
|
||||||
const normalizedData = normalizeStatusValue(data);
|
const normalizedData = normalizeParkTypeValue(normalizeStatusValue(data));
|
||||||
const sanitizedData = sanitizeDateFields(normalizedData);
|
const sanitizedData = sanitizeDateFields(normalizedData);
|
||||||
const filteredData = filterDatabaseFields(sanitizedData, PARK_FIELDS);
|
const filteredData = filterDatabaseFields(sanitizedData, PARK_FIELDS);
|
||||||
const { data: park, error } = await supabase
|
const { data: park, error } = await supabase
|
||||||
|
|||||||
Reference in New Issue
Block a user