mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 09:51:13 -05:00
Refactor: Implement Cloudflare Image Variants
This commit is contained in:
@@ -3,6 +3,7 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { Star, MapPin, Ruler, FerrisWheel } from 'lucide-react';
|
import { Star, MapPin, Ruler, FerrisWheel } from 'lucide-react';
|
||||||
import { Company } from '@/types/database';
|
import { Company } from '@/types/database';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||||
|
|
||||||
interface DesignerCardProps {
|
interface DesignerCardProps {
|
||||||
company: Company;
|
company: Company;
|
||||||
@@ -38,11 +39,12 @@ export function DesignerCard({ company }: DesignerCardProps) {
|
|||||||
|
|
||||||
{/* Logo or Icon */}
|
{/* Logo or Icon */}
|
||||||
<div className="relative z-10 flex items-center justify-center">
|
<div className="relative z-10 flex items-center justify-center">
|
||||||
{company.logo_url ? (
|
{(company.logo_url || (company as any).logo_image_id) ? (
|
||||||
<img
|
<img
|
||||||
src={company.logo_url}
|
src={company.logo_url || getCloudflareImageUrl((company as any).logo_image_id, 'logo')}
|
||||||
alt={`${company.name} logo`}
|
alt={`${company.name} logo`}
|
||||||
className="max-w-20 max-h-20 object-contain filter drop-shadow-sm"
|
className="max-w-20 max-h-20 object-contain filter drop-shadow-sm"
|
||||||
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-4 rounded-full bg-muted/30 backdrop-blur-sm border border-border/30">
|
<div className="p-4 rounded-full bg-muted/30 backdrop-blur-sm border border-border/30">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Company } from '@/types/database';
|
import { Company } from '@/types/database';
|
||||||
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||||
|
|
||||||
interface ManufacturerCardProps {
|
interface ManufacturerCardProps {
|
||||||
company: Company;
|
company: Company;
|
||||||
@@ -42,9 +43,15 @@ export function ManufacturerCard({ company }: ManufacturerCardProps) {
|
|||||||
<div className="aspect-video relative bg-gradient-to-br from-primary/20 via-primary/10 to-transparent overflow-hidden">
|
<div className="aspect-video relative bg-gradient-to-br from-primary/20 via-primary/10 to-transparent overflow-hidden">
|
||||||
{(company.card_image_url || company.card_image_id) ? (
|
{(company.card_image_url || company.card_image_id) ? (
|
||||||
<img
|
<img
|
||||||
src={company.card_image_url || `https://imagedelivery.net/${import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH}/${company.card_image_id}/public`}
|
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
|
||||||
|
` : undefined}
|
||||||
|
sizes="(max-width: 640px) 600px, 1200px"
|
||||||
alt={company.name}
|
alt={company.name}
|
||||||
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
||||||
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -59,12 +66,13 @@ export function ManufacturerCard({ company }: ManufacturerCardProps) {
|
|||||||
|
|
||||||
{/* Logo Display */}
|
{/* Logo Display */}
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
{company.logo_url ? (
|
{(company.logo_url || (company as any).logo_image_id) ? (
|
||||||
<div className="w-16 h-16 md:w-20 md:h-20 bg-background/90 rounded-xl overflow-hidden shadow-lg backdrop-blur-sm border border-border/50">
|
<div className="w-16 h-16 md:w-20 md:h-20 bg-background/90 rounded-xl overflow-hidden shadow-lg backdrop-blur-sm border border-border/50">
|
||||||
<img
|
<img
|
||||||
src={company.logo_url}
|
src={company.logo_url || getCloudflareImageUrl((company as any).logo_image_id, 'logo')}
|
||||||
alt={`${company.name} logo`}
|
alt={`${company.name} logo`}
|
||||||
className="w-full h-full object-contain p-2"
|
className="w-full h-full object-contain p-2"
|
||||||
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Card, CardContent } from '@/components/ui/card';
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Building, Star, MapPin } from 'lucide-react';
|
import { Building, Star, MapPin } from 'lucide-react';
|
||||||
import { Company } from '@/types/database';
|
import { Company } from '@/types/database';
|
||||||
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||||
|
|
||||||
interface OperatorCardProps {
|
interface OperatorCardProps {
|
||||||
company: Company;
|
company: Company;
|
||||||
@@ -29,9 +30,15 @@ const OperatorCard = ({ company }: OperatorCardProps) => {
|
|||||||
<div className="aspect-video relative bg-gradient-to-br from-primary/20 via-primary/10 to-transparent overflow-hidden">
|
<div className="aspect-video relative bg-gradient-to-br from-primary/20 via-primary/10 to-transparent overflow-hidden">
|
||||||
{(company.card_image_url || company.card_image_id) ? (
|
{(company.card_image_url || company.card_image_id) ? (
|
||||||
<img
|
<img
|
||||||
src={company.card_image_url || `https://imagedelivery.net/${import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH}/${company.card_image_id}/public`}
|
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
|
||||||
|
` : undefined}
|
||||||
|
sizes="(max-width: 640px) 600px, 1200px"
|
||||||
alt={company.name}
|
alt={company.name}
|
||||||
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
||||||
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -46,12 +53,13 @@ const OperatorCard = ({ company }: OperatorCardProps) => {
|
|||||||
|
|
||||||
{/* Logo Display */}
|
{/* Logo Display */}
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
{company.logo_url ? (
|
{(company.logo_url || (company as any).logo_image_id) ? (
|
||||||
<div className="w-20 h-20 bg-background/90 rounded-xl overflow-hidden shadow-lg backdrop-blur-sm border border-border/50">
|
<div className="w-20 h-20 bg-background/90 rounded-xl overflow-hidden shadow-lg backdrop-blur-sm border border-border/50">
|
||||||
<img
|
<img
|
||||||
src={company.logo_url}
|
src={company.logo_url || getCloudflareImageUrl((company as any).logo_image_id, 'logo')}
|
||||||
alt={`${company.name} logo`}
|
alt={`${company.name} logo`}
|
||||||
className="w-full h-full object-contain p-2"
|
className="w-full h-full object-contain p-2"
|
||||||
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Card, CardContent } from '@/components/ui/card';
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Building2, Star, MapPin } from 'lucide-react';
|
import { Building2, Star, MapPin } from 'lucide-react';
|
||||||
import { Company } from '@/types/database';
|
import { Company } from '@/types/database';
|
||||||
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||||
|
|
||||||
interface ParkOwnerCardProps {
|
interface ParkOwnerCardProps {
|
||||||
company: Company;
|
company: Company;
|
||||||
@@ -29,9 +30,15 @@ const ParkOwnerCard = ({ company }: ParkOwnerCardProps) => {
|
|||||||
<div className="aspect-video relative bg-gradient-to-br from-primary/20 via-primary/10 to-transparent overflow-hidden">
|
<div className="aspect-video relative bg-gradient-to-br from-primary/20 via-primary/10 to-transparent overflow-hidden">
|
||||||
{(company.card_image_url || company.card_image_id) ? (
|
{(company.card_image_url || company.card_image_id) ? (
|
||||||
<img
|
<img
|
||||||
src={company.card_image_url || `https://imagedelivery.net/${import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH}/${company.card_image_id}/public`}
|
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
|
||||||
|
` : undefined}
|
||||||
|
sizes="(max-width: 640px) 600px, 1200px"
|
||||||
alt={company.name}
|
alt={company.name}
|
||||||
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
||||||
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -46,12 +53,13 @@ const ParkOwnerCard = ({ company }: ParkOwnerCardProps) => {
|
|||||||
|
|
||||||
{/* Logo Display */}
|
{/* Logo Display */}
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
{company.logo_url ? (
|
{(company.logo_url || (company as any).logo_image_id) ? (
|
||||||
<div className="w-20 h-20 bg-background/90 rounded-xl overflow-hidden shadow-lg backdrop-blur-sm border border-border/50">
|
<div className="w-20 h-20 bg-background/90 rounded-xl overflow-hidden shadow-lg backdrop-blur-sm border border-border/50">
|
||||||
<img
|
<img
|
||||||
src={company.logo_url}
|
src={company.logo_url || getCloudflareImageUrl((company as any).logo_image_id, 'logo')}
|
||||||
alt={`${company.name} logo`}
|
alt={`${company.name} logo`}
|
||||||
className="w-full h-full object-contain p-2"
|
className="w-full h-full object-contain p-2"
|
||||||
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Park } from '@/types/database';
|
import { Park } from '@/types/database';
|
||||||
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||||
|
|
||||||
interface ParkCardProps {
|
interface ParkCardProps {
|
||||||
park: Park;
|
park: Park;
|
||||||
@@ -48,9 +49,15 @@ export function ParkCard({ park }: ParkCardProps) {
|
|||||||
<div className="aspect-[4/3] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 flex items-center justify-center relative">
|
<div className="aspect-[4/3] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 flex items-center justify-center relative">
|
||||||
{(park.card_image_url || park.card_image_id) ? (
|
{(park.card_image_url || park.card_image_id) ? (
|
||||||
<img
|
<img
|
||||||
src={park.card_image_url || `https://imagedelivery.net/${import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH}/${park.card_image_id}/public`}
|
src={park.card_image_url || getCloudflareImageUrl(park.card_image_id, 'card')}
|
||||||
|
srcSet={park.card_image_id ? `
|
||||||
|
${getCloudflareImageUrl(park.card_image_id, 'cardthumb')} 600w,
|
||||||
|
${getCloudflareImageUrl(park.card_image_id, 'card')} 1200w
|
||||||
|
` : undefined}
|
||||||
|
sizes="(max-width: 640px) 600px, 1200px"
|
||||||
alt={park.name}
|
alt={park.name}
|
||||||
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
||||||
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="opacity-50 flex items-center justify-center">
|
<div className="opacity-50 flex items-center justify-center">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { Star, MapPin, Clock, Zap, FerrisWheel, Waves, Theater, Train } from 'lucide-react';
|
import { Star, MapPin, Clock, Zap, FerrisWheel, Waves, Theater, Train } from 'lucide-react';
|
||||||
import { MeasurementDisplay } from '@/components/ui/measurement-display';
|
import { MeasurementDisplay } from '@/components/ui/measurement-display';
|
||||||
import { Ride } from '@/types/database';
|
import { Ride } from '@/types/database';
|
||||||
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||||
|
|
||||||
interface RideCardProps {
|
interface RideCardProps {
|
||||||
ride: Ride;
|
ride: Ride;
|
||||||
@@ -57,9 +58,15 @@ export function RideCard({ ride, showParkName = true, className, parkSlug }: Rid
|
|||||||
<div className="aspect-[4/3] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 flex items-center justify-center relative">
|
<div className="aspect-[4/3] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 flex items-center justify-center relative">
|
||||||
{(ride.card_image_url || ride.card_image_id || ride.image_url) ? (
|
{(ride.card_image_url || ride.card_image_id || ride.image_url) ? (
|
||||||
<img
|
<img
|
||||||
src={ride.card_image_url || (ride.card_image_id ? `https://imagedelivery.net/${import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH}/${ride.card_image_id}/public` : ride.image_url)}
|
src={ride.card_image_url || getCloudflareImageUrl(ride.card_image_id, 'card') || ride.image_url}
|
||||||
|
srcSet={ride.card_image_id ? `
|
||||||
|
${getCloudflareImageUrl(ride.card_image_id, 'cardthumb')} 600w,
|
||||||
|
${getCloudflareImageUrl(ride.card_image_id, 'card')} 1200w
|
||||||
|
` : undefined}
|
||||||
|
sizes="(max-width: 640px) 600px, 1200px"
|
||||||
alt={ride.name}
|
alt={ride.name}
|
||||||
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
||||||
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="opacity-50 flex items-center justify-center">
|
<div className="opacity-50 flex items-center justify-center">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { FerrisWheel } from 'lucide-react';
|
import { FerrisWheel } from 'lucide-react';
|
||||||
import { RideModel } from '@/types/database';
|
import { RideModel } from '@/types/database';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||||
|
|
||||||
interface RideModelCardProps {
|
interface RideModelCardProps {
|
||||||
model: RideModel;
|
model: RideModel;
|
||||||
@@ -45,9 +46,15 @@ export function RideModelCard({ model, manufacturerSlug }: RideModelCardProps) {
|
|||||||
>
|
>
|
||||||
{(cardImageUrl || cardImageId) ? (
|
{(cardImageUrl || cardImageId) ? (
|
||||||
<img
|
<img
|
||||||
src={cardImageUrl || `https://imagedelivery.net/${import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH}/${cardImageId}/public`}
|
src={cardImageUrl || getCloudflareImageUrl(cardImageId, 'card')}
|
||||||
|
srcSet={cardImageId ? `
|
||||||
|
${getCloudflareImageUrl(cardImageId, 'cardthumb')} 600w,
|
||||||
|
${getCloudflareImageUrl(cardImageId, 'card')} 1200w
|
||||||
|
` : undefined}
|
||||||
|
sizes="(max-width: 640px) 600px, 1200px"
|
||||||
alt={model.name}
|
alt={model.name}
|
||||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||||
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Image as ImageIcon, ImagePlus, X } from 'lucide-react';
|
import { Image as ImageIcon, ImagePlus, X } from 'lucide-react';
|
||||||
import { UppyPhotoUpload } from './UppyPhotoUpload';
|
import { UppyPhotoUpload } from './UppyPhotoUpload';
|
||||||
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||||
|
|
||||||
export type ImageType = 'logo' | 'banner' | 'card';
|
export type ImageType = 'logo' | 'banner' | 'card';
|
||||||
|
|
||||||
@@ -140,7 +141,15 @@ export function EntityImageUploader({
|
|||||||
{hasImage ? (
|
{hasImage ? (
|
||||||
<div className="relative aspect-[16/9] bg-muted">
|
<div className="relative aspect-[16/9] bg-muted">
|
||||||
<img
|
<img
|
||||||
src={currentImage.url}
|
src={
|
||||||
|
currentImage.url || (
|
||||||
|
type === 'logo'
|
||||||
|
? getCloudflareImageUrl(currentImage.id, 'logo')
|
||||||
|
: type === 'banner'
|
||||||
|
? getCloudflareImageUrl(currentImage.id, 'banner')
|
||||||
|
: getCloudflareImageUrl(currentImage.id, 'card')
|
||||||
|
)
|
||||||
|
}
|
||||||
alt={`${spec.label} preview`}
|
alt={`${spec.label} preview`}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -142,7 +142,8 @@ export function UppyPhotoUpload({
|
|||||||
if (statusResponse.ok) {
|
if (statusResponse.ok) {
|
||||||
const status: UploadSuccessResponse = await statusResponse.json();
|
const status: UploadSuccessResponse = await statusResponse.json();
|
||||||
if (status.uploaded && status.urls) {
|
if (status.uploaded && status.urls) {
|
||||||
return status.urls.public;
|
const CLOUDFLARE_ACCOUNT_HASH = import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH;
|
||||||
|
return `https://imagedelivery.net/${CLOUDFLARE_ACCOUNT_HASH}/${cloudflareId}/public`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
61
src/lib/cloudflareImageUtils.ts
Normal file
61
src/lib/cloudflareImageUtils.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Cloudflare Images variant utilities
|
||||||
|
* Generates properly formatted URLs for Cloudflare Image variants
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type CloudflareVariant =
|
||||||
|
| 'avatar'
|
||||||
|
| 'banner'
|
||||||
|
| 'bannermobile'
|
||||||
|
| 'card'
|
||||||
|
| 'cardthumb'
|
||||||
|
| 'logo'
|
||||||
|
| 'public';
|
||||||
|
|
||||||
|
const CLOUDFLARE_ACCOUNT_HASH = import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a Cloudflare image URL with specified variant
|
||||||
|
*/
|
||||||
|
export function getCloudflareImageUrl(
|
||||||
|
imageId: string | undefined,
|
||||||
|
variant: CloudflareVariant = 'public'
|
||||||
|
): string | undefined {
|
||||||
|
if (!imageId) return undefined;
|
||||||
|
return `https://imagedelivery.net/${CLOUDFLARE_ACCOUNT_HASH}/${imageId}/${variant}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate responsive image srcset for card images
|
||||||
|
* Useful for <img srcset> elements
|
||||||
|
*/
|
||||||
|
export function getCloudflareImageSrcSet(imageId: string | undefined): string | undefined {
|
||||||
|
if (!imageId) return undefined;
|
||||||
|
|
||||||
|
return [
|
||||||
|
`${getCloudflareImageUrl(imageId, 'cardthumb')} 600w`,
|
||||||
|
`${getCloudflareImageUrl(imageId, 'card')} 1200w`,
|
||||||
|
`${getCloudflareImageUrl(imageId, 'public')} 1366w`
|
||||||
|
].join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get responsive banner URLs for mobile and desktop
|
||||||
|
*/
|
||||||
|
export function getBannerUrls(imageId: string | undefined) {
|
||||||
|
if (!imageId) return { mobile: undefined, desktop: undefined };
|
||||||
|
|
||||||
|
return {
|
||||||
|
mobile: getCloudflareImageUrl(imageId, 'bannermobile'),
|
||||||
|
desktop: getCloudflareImageUrl(imageId, 'banner')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Cloudflare image ID from various URL formats
|
||||||
|
*/
|
||||||
|
export function extractCloudflareImageId(url: string): string | null {
|
||||||
|
// Match imagedelivery.net URLs
|
||||||
|
const match = url.match(/imagedelivery\.net\/[^\/]+\/([a-f0-9-]+)\//i);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}
|
||||||
@@ -60,9 +60,11 @@ export async function uploadPendingImages(images: UploadedImage[]): Promise<Uplo
|
|||||||
// Clean up object URL
|
// Clean up object URL
|
||||||
URL.revokeObjectURL(image.url);
|
URL.revokeObjectURL(image.url);
|
||||||
|
|
||||||
|
const CLOUDFLARE_ACCOUNT_HASH = import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH;
|
||||||
|
|
||||||
// Step 3: Return uploaded image metadata with wasNewlyUploaded flag
|
// Step 3: Return uploaded image metadata with wasNewlyUploaded flag
|
||||||
return {
|
return {
|
||||||
url: result.result.variants[0], // Use first variant (usually the original)
|
url: `https://imagedelivery.net/${CLOUDFLARE_ACCOUNT_HASH}/${result.result.id}/public`,
|
||||||
cloudflare_id: result.result.id,
|
cloudflare_id: result.result.id,
|
||||||
caption: image.caption,
|
caption: image.caption,
|
||||||
isLocal: false,
|
isLocal: false,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Header } from '@/components/layout/Header';
|
import { Header } from '@/components/layout/Header';
|
||||||
|
import { getBannerUrls } from '@/lib/cloudflareImageUtils';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
@@ -169,11 +170,18 @@ export default function DesignerDetail() {
|
|||||||
<div className="relative mb-8">
|
<div className="relative mb-8">
|
||||||
<div className="aspect-[21/9] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 rounded-lg overflow-hidden relative">
|
<div className="aspect-[21/9] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 rounded-lg overflow-hidden relative">
|
||||||
{(designer.banner_image_url || designer.banner_image_id) ? (
|
{(designer.banner_image_url || designer.banner_image_id) ? (
|
||||||
<img
|
<picture>
|
||||||
src={designer.banner_image_url || `https://imagedelivery.net/${import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH}/${designer.banner_image_id}/public`}
|
<source
|
||||||
alt={designer.name}
|
media="(max-width: 768px)"
|
||||||
className="w-full h-full object-cover"
|
srcSet={getBannerUrls(designer.banner_image_id).mobile || designer.banner_image_url}
|
||||||
/>
|
/>
|
||||||
|
<img
|
||||||
|
src={getBannerUrls(designer.banner_image_id).desktop || designer.banner_image_url}
|
||||||
|
alt={designer.name}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
|
</picture>
|
||||||
) : designer.logo_url ? (
|
) : designer.logo_url ? (
|
||||||
<div className="flex items-center justify-center h-full bg-background/90">
|
<div className="flex items-center justify-center h-full bg-background/90">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Header } from '@/components/layout/Header';
|
import { Header } from '@/components/layout/Header';
|
||||||
|
import { getBannerUrls } from '@/lib/cloudflareImageUtils';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
@@ -181,11 +182,18 @@ export default function ManufacturerDetail() {
|
|||||||
<div className="relative mb-4 md:mb-8">
|
<div className="relative mb-4 md:mb-8">
|
||||||
<div className="aspect-[16/9] md:aspect-[21/9] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 rounded-lg overflow-hidden relative">
|
<div className="aspect-[16/9] md:aspect-[21/9] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 rounded-lg overflow-hidden relative">
|
||||||
{(manufacturer.banner_image_url || manufacturer.banner_image_id) ? (
|
{(manufacturer.banner_image_url || manufacturer.banner_image_id) ? (
|
||||||
<img
|
<picture>
|
||||||
src={manufacturer.banner_image_url || `https://imagedelivery.net/${import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH}/${manufacturer.banner_image_id}/public`}
|
<source
|
||||||
alt={manufacturer.name}
|
media="(max-width: 768px)"
|
||||||
className="w-full h-full object-cover"
|
srcSet={getBannerUrls(manufacturer.banner_image_id).mobile || manufacturer.banner_image_url}
|
||||||
/>
|
/>
|
||||||
|
<img
|
||||||
|
src={getBannerUrls(manufacturer.banner_image_id).desktop || manufacturer.banner_image_url}
|
||||||
|
alt={manufacturer.name}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
|
</picture>
|
||||||
) : manufacturer.logo_url ? (
|
) : manufacturer.logo_url ? (
|
||||||
<div className="flex items-center justify-center h-full bg-background/90">
|
<div className="flex items-center justify-center h-full bg-background/90">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Header } from '@/components/layout/Header';
|
import { Header } from '@/components/layout/Header';
|
||||||
|
import { getBannerUrls } from '@/lib/cloudflareImageUtils';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
@@ -214,11 +215,18 @@ export default function OperatorDetail() {
|
|||||||
<div className="relative mb-8">
|
<div className="relative mb-8">
|
||||||
<div className="aspect-[21/9] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 rounded-lg overflow-hidden relative">
|
<div className="aspect-[21/9] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 rounded-lg overflow-hidden relative">
|
||||||
{(operator.banner_image_url || operator.banner_image_id) ? (
|
{(operator.banner_image_url || operator.banner_image_id) ? (
|
||||||
<img
|
<picture>
|
||||||
src={operator.banner_image_url || `https://imagedelivery.net/${import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH}/${operator.banner_image_id}/public`}
|
<source
|
||||||
alt={operator.name}
|
media="(max-width: 768px)"
|
||||||
className="w-full h-full object-cover"
|
srcSet={getBannerUrls(operator.banner_image_id).mobile || operator.banner_image_url}
|
||||||
/>
|
/>
|
||||||
|
<img
|
||||||
|
src={getBannerUrls(operator.banner_image_id).desktop || operator.banner_image_url}
|
||||||
|
alt={operator.name}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
|
</picture>
|
||||||
) : operator.logo_url ? (
|
) : operator.logo_url ? (
|
||||||
<div className="flex items-center justify-center h-full bg-background/90">
|
<div className="flex items-center justify-center h-full bg-background/90">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Header } from '@/components/layout/Header';
|
import { Header } from '@/components/layout/Header';
|
||||||
|
import { getBannerUrls } from '@/lib/cloudflareImageUtils';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@@ -244,11 +245,18 @@ export default function ParkDetail() {
|
|||||||
<div className="relative mb-8">
|
<div className="relative mb-8">
|
||||||
<div className="aspect-[21/9] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 rounded-lg overflow-hidden relative">
|
<div className="aspect-[21/9] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 rounded-lg overflow-hidden relative">
|
||||||
{(park.banner_image_url || park.banner_image_id) ? (
|
{(park.banner_image_url || park.banner_image_id) ? (
|
||||||
<img
|
<picture>
|
||||||
src={park.banner_image_url || `https://imagedelivery.net/${import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH}/${park.banner_image_id}/public`}
|
<source
|
||||||
alt={park.name}
|
media="(max-width: 768px)"
|
||||||
className="w-full h-full object-cover"
|
srcSet={getBannerUrls(park.banner_image_id).mobile || park.banner_image_url}
|
||||||
/>
|
/>
|
||||||
|
<img
|
||||||
|
src={getBannerUrls(park.banner_image_id).desktop || park.banner_image_url}
|
||||||
|
alt={park.name}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
|
</picture>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<div className="opacity-50">
|
<div className="opacity-50">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Header } from '@/components/layout/Header';
|
import { Header } from '@/components/layout/Header';
|
||||||
|
import { getBannerUrls } from '@/lib/cloudflareImageUtils';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
@@ -214,11 +215,18 @@ export default function PropertyOwnerDetail() {
|
|||||||
<div className="relative mb-8">
|
<div className="relative mb-8">
|
||||||
<div className="aspect-[21/9] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 rounded-lg overflow-hidden relative">
|
<div className="aspect-[21/9] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 rounded-lg overflow-hidden relative">
|
||||||
{(owner.banner_image_url || owner.banner_image_id) ? (
|
{(owner.banner_image_url || owner.banner_image_id) ? (
|
||||||
<img
|
<picture>
|
||||||
src={owner.banner_image_url || `https://imagedelivery.net/${import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH}/${owner.banner_image_id}/public`}
|
<source
|
||||||
alt={owner.name}
|
media="(max-width: 768px)"
|
||||||
className="w-full h-full object-cover"
|
srcSet={getBannerUrls(owner.banner_image_id).mobile || owner.banner_image_url}
|
||||||
/>
|
/>
|
||||||
|
<img
|
||||||
|
src={getBannerUrls(owner.banner_image_id).desktop || owner.banner_image_url}
|
||||||
|
alt={owner.name}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
|
</picture>
|
||||||
) : owner.logo_url ? (
|
) : owner.logo_url ? (
|
||||||
<div className="flex items-center justify-center h-full bg-background/90">
|
<div className="flex items-center justify-center h-full bg-background/90">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Header } from '@/components/layout/Header';
|
import { Header } from '@/components/layout/Header';
|
||||||
|
import { getBannerUrls } from '@/lib/cloudflareImageUtils';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@@ -241,11 +242,18 @@ export default function RideDetail() {
|
|||||||
<div className="relative mb-8">
|
<div className="relative mb-8">
|
||||||
<div className="aspect-[21/9] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 rounded-lg overflow-hidden relative">
|
<div className="aspect-[21/9] bg-gradient-to-br from-primary/20 via-secondary/20 to-accent/20 rounded-lg overflow-hidden relative">
|
||||||
{(ride.banner_image_url || ride.banner_image_id) ? (
|
{(ride.banner_image_url || ride.banner_image_id) ? (
|
||||||
<img
|
<picture>
|
||||||
src={ride.banner_image_url || `https://imagedelivery.net/${import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH}/${ride.banner_image_id}/public`}
|
<source
|
||||||
alt={ride.name}
|
media="(max-width: 768px)"
|
||||||
className="w-full h-full object-cover"
|
srcSet={getBannerUrls(ride.banner_image_id).mobile || ride.banner_image_url}
|
||||||
/>
|
/>
|
||||||
|
<img
|
||||||
|
src={getBannerUrls(ride.banner_image_id).desktop || ride.banner_image_url}
|
||||||
|
alt={ride.name}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
|
</picture>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<div className="opacity-50">
|
<div className="opacity-50">
|
||||||
|
|||||||
Reference in New Issue
Block a user