⚠️ Archived: This document is kept for historical reference and may be outdated.
See: Documentation Hub
Date: 2024-01-XX
Purpose: Analyze current image upload handling, identify mobile camera issues, and propose normalization solution
Status: Analysis Complete - Ready for Implementation Proposal
Problem Identified: Discart-me uploads images directly from mobile cameras without any preprocessing, resulting in:
Solution Available: QRACK already has image processing logic in src/lib/imageUtils.ts that is NOT being used by Discart-me.
Recommendation: Extract and generalize the image processing logic from QRACK into a shared library (src/lib/image/) that both systems can use.
Location: src/lib/imageUtils.ts
Features:
Usage:
app/qrack/items/[id]/edit/page.tsxapp/qrack/containers/[id]/items/page.tsxresizeAndCompressImage() before uploadCurrent Implementation:
// QRACK usage pattern
const compressedDataUrl = await resizeAndCompressImage(file, {
maxWidth: 1200,
maxHeight: 1200,
quality: 0.8,
maxSizeKB: 500,
outputFormat: 'image/jpeg',
});
// Then sends data URL to API
Limitations:
Location: src/lib/upload.ts
Features:
image/* files accepted)Usage:
uploadDiscartItemImages() - uploads File[] directly via FormDataapp/discart-me/items/new/page.tsxapp/discart-me/items/[id]/edit/page.tsxsrc/components/discart-me/ItemForm.tsx (drag & drop area)Current Implementation:
// Discart-me usage pattern (NO COMPRESSION)
const files = Array.from(e.dataTransfer.files).filter(
(file) => file.type.startsWith('image/')
);
// Files uploaded directly via FormData
await uploadDiscartItemImages(values.imageFiles, token);
Problems:
Location: app/discart-me/profile/page.tsx
Validation:
file.type.startsWith('image/')file.size > 2 * 1024 * 1024 (2MB max)uploadAvatar() from src/lib/upload.tsNote: Even with 2MB limit, images are not compressed/resized before upload.
src/lib/imageUtils.ts)Functions Available:
resizeAndCompressImage(file, options): Promise<string>
File objectcanvas.toBlob() with quality settingvalidateImageFile(file, maxSizeMB): { valid, error? }
getImageDimensions(file): Promise<{ width, height }>
formatFileSize(bytes): string
app/qrack/items/[id]/edit/page.tsx)
resizeAndCompressImage() ✅app/qrack/containers/[id]/items/page.tsx)
resizeAndCompressImage() ✅app/discart-me/items/new/page.tsx)
uploadDiscartItemImages()app/discart-me/items/[id]/edit/page.tsx)
uploadDiscartItemImages()app/discart-me/profile/page.tsx)
uploadAvatar()src/components/discart-me/ItemForm.tsx)
file.type.startsWith('image/') onlyimageUtils.ts be Extracted?✅ YES - The logic is generic enough but needs modifications:
Already Reusable:
validateImageFile() - completely genericgetImageDimensions() - completely genericformatFileSize() - completely genericNeeds Modification:
resizeAndCompressImage() - needs to optionally return File object instead of data URLCurrent Coupling:
src/lib/image/processImage.ts instead of imageUtils.tsFor Marketplace Items (Discart-me):
For Container Items (QRACK):
For Avatars:
Problem: Mobile cameras store rotation in EXIF, canvas doesn’t auto-apply it.
Solution Options:
Recommendation: Start without EXIF handling (can add later), but document the limitation.
src/lib/image/
├── processImage.ts # Main processing function
├── types.ts # TypeScript interfaces
├── constants.ts # Default configurations per use case
└── utils.ts # Helper functions (validate, format, etc.)
types.ts)export interface ImageProcessingOptions {
maxWidth?: number;
maxHeight?: number;
quality?: number; // 0.1 to 1.0
maxSizeKB?: number; // Target max file size in KB
outputFormat?: 'image/jpeg' | 'image/png' | 'image/webp';
outputType?: 'dataUrl' | 'file'; // NEW: support both outputs
maintainAspectRatio?: boolean; // Default: true
// EXIF handling (future):
// correctOrientation?: boolean; // Default: false (requires library)
}
export interface ProcessedImage {
dataUrl?: string; // Present if outputType === 'dataUrl'
file?: File; // Present if outputType === 'file'
originalSize: number;
processedSize: number;
originalDimensions: { width: number; height: number };
processedDimensions: { width: number; height: number };
}
export interface ImageValidationResult {
valid: boolean;
error?: string;
}
processImage.ts)/**
* Process an image file: resize, compress, and optionally correct orientation
*
* @param file - Input image file
* @param options - Processing options
* @returns Processed image (data URL or File object)
*/
export async function processImage(
file: File,
options: ImageProcessingOptions = {}
): Promise<ProcessedImage>
/**
* Validate image file type and size
*/
export function validateImageFile(
file: File,
maxSizeMB?: number
): ImageValidationResult
/**
* Get image dimensions without fully loading
*/
export function getImageDimensions(
file: File
): Promise<{ width: number; height: number }>
/**
* Format bytes to human-readable string
*/
export function formatFileSize(bytes: number): string
constants.ts)export const IMAGE_PRESETS = {
marketplace: {
maxWidth: 2048,
maxHeight: 2048,
quality: 0.85,
maxSizeKB: 1500, // 1.5MB
outputFormat: 'image/jpeg' as const,
},
container: {
maxWidth: 1200,
maxHeight: 1200,
quality: 0.8,
maxSizeKB: 500,
outputFormat: 'image/jpeg' as const,
},
avatar: {
maxWidth: 512,
maxHeight: 512,
quality: 0.8,
maxSizeKB: 200,
outputFormat: 'image/jpeg' as const,
},
} as const;
import { processImage, IMAGE_PRESETS } from '@/src/lib/image';
const processed = await processImage(file, {
...IMAGE_PRESETS.marketplace,
outputType: 'file', // Returns File object for FormData
});
// Use processed.file for upload
await uploadDiscartItemImages([processed.file], token);
import { processImage, IMAGE_PRESETS } from '@/src/lib/image';
const processed = await processImage(file, {
...IMAGE_PRESETS.container,
outputType: 'dataUrl', // Returns data URL (current behavior)
});
// Use processed.dataUrl for API
setImageUrl(processed.dataUrl);
Integration Points:
src/components/discart-me/ItemForm.tsx)
ImageUploadAreaapp/discart-me/profile/page.tsx)
IMAGE_PRESETS.avatar presetIMAGE_PRESETS.containerCurrent: Async (uses Promise) ✅
Recommendation: Keep async (image processing is inherently async due to FileReader and canvas operations)
Current: ✅ Browser-only (FileReader, Image, Canvas APIs)
Recommendation: Document clearly, use 'use client' directive, add runtime checks if needed
Risk: Migrating QRACK from imageUtils.ts to new library could break existing code.
Mitigation:
imageUtils.ts as a wrapper/thin layer that imports from new libraryRisk: Processing multiple images could block UI thread.
Mitigation:
Risk: Large images loaded into memory (FileReader, Image, Canvas).
Mitigation:
Risk: Canvas/FileReader APIs may not work in older browsers.
Mitigation:
Risk: Images may appear rotated incorrectly on mobile.
Current: Not handled (document as limitation)
Future: Add EXIF-JS or browser-image-compression library
Risk: HEIC format (iOS) may not be supported by Canvas.
Mitigation:
Action: Extract image processing logic from src/lib/imageUtils.ts into a shared library src/lib/image/ with the following enhancements:
Phase 1: Create Shared Library
imageUtils.ts → src/lib/image/processImage.tsPhase 2: Integrate in Discart-me
Phase 3: Migrate QRACK (optional)
imageUtils.ts (or keep as wrapper)Existing logic is solid:
Only needs generalization:
Could use browser-image-compression (npm package):
Recommendation: Stick with custom solution (already works, just needs generalization).
The codebase already has image processing logic in QRACK that is not being used by Discart-me. The recommended approach is to extract and generalize this logic into a shared library that supports both systems while maintaining backward compatibility.
The primary gap is that Discart-me uploads raw camera images without preprocessing, causing the mobile upload performance issues. The solution exists but needs to be shared and adapted for Discart-me’s File-based upload flow.
Next Step: Implementation proposal (separate task).
Document Status: ✅ Analysis Complete
Ready For: Implementation Proposal Phase