a3638fe954
Test and Lint / backend-test (push) Successful in 1m11s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m17s
Version and Release / version-bump (push) Successful in 43s
Version and Release / trigger-drone (push) Successful in 3s
- Add URL utility functions for building resource URLs - Update all components to use relative URLs in production - Add production deployment documentation - Update nginx config to proxy all required endpoints - Add .env.production.example with proper configuration
117 lines
3.1 KiB
TypeScript
117 lines
3.1 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { getAuthToken } from '../../config/api';
|
|
import { buildResourceUrl } from '../../utils/url';
|
|
|
|
interface AuthenticatedImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
|
src: string;
|
|
fallbackSrc?: string;
|
|
useWatermark?: boolean;
|
|
isGallery?: boolean;
|
|
}
|
|
|
|
export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
|
|
src,
|
|
fallbackSrc,
|
|
alt,
|
|
useWatermark = false,
|
|
isGallery = false,
|
|
...props
|
|
}) => {
|
|
const [imageSrc, setImageSrc] = useState<string>('');
|
|
const [error, setError] = useState(false);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
let objectUrl: string | null = null;
|
|
|
|
// Determine which token to use based on context
|
|
let token: string | undefined;
|
|
|
|
if (isGallery) {
|
|
// For gallery images, get the gallery-specific token
|
|
const pathParts = window.location.pathname.split('/');
|
|
if (pathParts[1] === 'gallery' && pathParts[2]) {
|
|
const gallerySlug = pathParts[2];
|
|
token = localStorage.getItem(`gallery_token_${gallerySlug}`) || undefined;
|
|
}
|
|
} else {
|
|
// For admin images, use the admin token
|
|
token = getAuthToken(true);
|
|
}
|
|
|
|
if (!src) {
|
|
setImageSrc(fallbackSrc || '');
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
|
|
if (!token) {
|
|
console.warn('No auth token found for image:', src);
|
|
setImageSrc(fallbackSrc || '');
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
setError(false);
|
|
|
|
// Create a new URL with auth header
|
|
const fetchImage = async () => {
|
|
try {
|
|
// Use the src as-is since it should already be the correct endpoint
|
|
let imageUrl = src;
|
|
|
|
// Build full URL for the image
|
|
const fullImageUrl = imageUrl.startsWith('/') ? buildResourceUrl(imageUrl) : imageUrl;
|
|
|
|
console.log('Fetching authenticated image:', fullImageUrl);
|
|
const response = await fetch(fullImageUrl, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
const blob = await response.blob();
|
|
objectUrl = URL.createObjectURL(blob);
|
|
setImageSrc(objectUrl);
|
|
setIsLoading(false);
|
|
} catch (err) {
|
|
console.error('Failed to load image:', src, err);
|
|
setError(true);
|
|
setImageSrc(fallbackSrc || '');
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchImage();
|
|
|
|
// Cleanup function
|
|
return () => {
|
|
if (objectUrl) {
|
|
URL.revokeObjectURL(objectUrl);
|
|
}
|
|
};
|
|
}, [src, fallbackSrc, useWatermark, isGallery]);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className={props.className} style={{ backgroundColor: '#f3f4f6', ...props.style }}>
|
|
{/* Show a placeholder while loading */}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error && fallbackSrc) {
|
|
return <img src={fallbackSrc} alt={alt} {...props} />;
|
|
}
|
|
|
|
if (!imageSrc) {
|
|
return null;
|
|
}
|
|
|
|
return <img src={imageSrc} alt={alt} {...props} />;
|
|
}; |