Files
picpeak/frontend/src/components/admin/AdminAuthenticatedImage.tsx
T
paul 69538b86ea
Test and Lint / backend-test (pull_request) Successful in 1m24s
Test and Lint / frontend-test (pull_request) Successful in 1m59s
continuous-integration/drone/pr Build is passing
Fix mobile overlay and deps per #43
2025-10-29 12:19:43 +01:00

80 lines
1.8 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { api } from '../../config/api';
interface AdminAuthenticatedImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
src: string;
fallback?: React.ReactNode;
}
export const AdminAuthenticatedImage: React.FC<AdminAuthenticatedImageProps> = ({
src,
fallback,
alt,
...props
}) => {
const [imageSrc, setImageSrc] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
useEffect(() => {
let cancelled = false;
let objectUrl: string | null = null;
const loadImage = async () => {
try {
setLoading(true);
setError(false);
setImageSrc(null);
// Make authenticated request to get the image
const response = await api.get(src, {
responseType: 'blob',
});
if (!cancelled) {
// Create object URL from blob
objectUrl = URL.createObjectURL(response.data);
setImageSrc(objectUrl);
setLoading(false);
}
} catch {
// Image loading failed - handled by error state
if (!cancelled) {
setError(true);
setLoading(false);
}
}
};
if (src) {
loadImage();
}
// Cleanup function
return () => {
cancelled = true;
if (objectUrl) {
URL.revokeObjectURL(objectUrl);
}
};
}, [src]);
if (loading) {
return (
<div className="w-full h-full bg-neutral-200 animate-pulse" />
);
}
if (error) {
return fallback ? (
<>{fallback}</>
) : (
<div className="w-full h-full bg-neutral-100 flex items-center justify-center text-neutral-400">
<span className="text-xs">Failed to load</span>
</div>
);
}
return <img src={imageSrc || ''} alt={alt} {...props} />;
};