feat: Return S3 keys from image uploads and resolve them for display previews in the CMS.

This commit is contained in:
pradeepkumar
2026-03-05 05:51:45 +05:30
parent d71b9a27c9
commit bf70ea1671

View File

@@ -289,15 +289,14 @@ export default function CmsPage() {
}
}
// Generic image upload — returns the presigned download URL
// Generic image upload — returns the S3 key for permanent storage
async function handleImageUpload(file: File): Promise<string> {
setIsUploadingImage(true);
setModalError('');
try {
const { uploadUrl, key } = await uploadService.getPresignedUploadUrl(file.name, file.type, 'properties');
await uploadService.uploadFileToS3(uploadUrl, file);
const downloadUrl = await uploadService.getPresignedDownloadUrl(key);
return downloadUrl;
return key;
} catch (err) {
setModalError(getErrorMessage(err));
return '';
@@ -306,6 +305,19 @@ export default function CmsPage() {
}
}
// Resolve an S3 key or URL for display — returns a viewable URL
async function resolveImageUrl(value: string): Promise<string> {
if (!value) return '';
// Already a full URL or local asset path — use directly
if (value.startsWith('http') || value.startsWith('/')) return value;
// Treat as S3 key — get presigned download URL
try {
return await uploadService.getPresignedDownloadUrl(value);
} catch {
return value;
}
}
// ---------------------------------------------------------------------------
// Section-specific form renderers
// ---------------------------------------------------------------------------
@@ -649,14 +661,27 @@ export default function CmsPage() {
);
}
// Reusable image upload field
function renderImageUploadField(label: string, currentUrl: string, onUrlChange: (url: string) => void) {
// Reusable image upload field with S3 key resolution for preview
function ImageUploadField({ label, currentUrl, onUrlChange }: { label: string; currentUrl: string; onUrlChange: (url: string) => void }) {
const [previewUrl, setPreviewUrl] = useState('');
useEffect(() => {
if (!currentUrl) { setPreviewUrl(''); return; }
// If it's already a full URL or local path, use directly
if (currentUrl.startsWith('http') || currentUrl.startsWith('/')) {
setPreviewUrl(currentUrl);
} else {
// S3 key — resolve to presigned URL for preview
resolveImageUrl(currentUrl).then(setPreviewUrl);
}
}, [currentUrl]);
return (
<div>
<label className="block text-sm font-medium text-[#00293d] mb-1">{label}</label>
{currentUrl && (
{previewUrl && (
<div className="mb-3 relative rounded-lg overflow-hidden border border-[#e5e7eb]">
<img src={currentUrl} alt="Preview" className="w-full h-48 object-cover" onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }} />
<img src={previewUrl} alt="Preview" className="w-full h-48 object-cover" onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }} />
<button type="button" onClick={() => onUrlChange('')} className="absolute top-2 right-2 px-2 py-1 bg-red-600 text-white text-xs rounded-lg hover:bg-red-700 transition-colors">Remove</button>
</div>
)}
@@ -671,11 +696,15 @@ export default function CmsPage() {
</label>
<span className="text-xs text-[#666666] self-center">or enter URL below</span>
</div>
<input type="text" className={inputCls} value={currentUrl} onChange={(e) => onUrlChange(e.target.value)} placeholder="https://..." />
<input type="text" className={inputCls} value={currentUrl} onChange={(e) => onUrlChange(e.target.value)} placeholder="S3 key or https://..." />
</div>
);
}
function renderImageUploadField(label: string, currentUrl: string, onUrlChange: (url: string) => void) {
return <ImageUploadField label={label} currentUrl={currentUrl} onUrlChange={onUrlChange} />;
}
// --- About Us form renderers ---
function renderAboutHeroForm() {