-
Title *
-
setTitle(e.target.value)}
- placeholder="Give your photos a descriptive title"
- maxLength={100}
- required
- />
-
- {title.length}/100 characters
-
-
+
+ Title *
+
+
setTitle(e.target.value)}
+ placeholder="Give your photos a descriptive title"
+ maxLength={100}
+ required
+ className="transition-all duration-200 focus:ring-2 focus:ring-primary/20"
+ />
+
+ {title.length}/100 characters
+
+
-
Photos
-
+
+
+
+
+
Photos *
{uploadedUrls.length > 0 && (
-
- {uploadedUrls.length} photo(s) uploaded and ready to submit
-
+
+ {uploadedUrls.length} photo{uploadedUrls.length !== 1 ? 's' : ''} selected
+
)}
+
-
-
+
+
+
- {isSubmitting ? 'Submitting...' : 'Submit Photos'}
+ {isSubmitting ? (
+
+
+ Submitting Photos...
+
+ ) : (
+
+
+ Submit {uploadedUrls.length} Photo{uploadedUrls.length !== 1 ? 's' : ''}
+
+ )}
+
+
+
+
Your submission will be reviewed and published within 24-48 hours
+
-
-
- All photo submissions are reviewed before being published. Please ensure your photos
- follow our community guidelines and are appropriate for all audiences.
-
-
+
);
}
\ No newline at end of file
diff --git a/src/components/upload/UppyPhotoUpload.tsx b/src/components/upload/UppyPhotoUpload.tsx
index 57b0fe0f..d18fe310 100644
--- a/src/components/upload/UppyPhotoUpload.tsx
+++ b/src/components/upload/UppyPhotoUpload.tsx
@@ -7,9 +7,9 @@ import { DashboardModal } from '@uppy/react';
import { supabase } from '@/integrations/supabase/client';
import { useToast } from '@/hooks/use-toast';
import { Button } from '@/components/ui/button';
-import { Upload, Image as ImageIcon } from 'lucide-react';
-
-// CSS imports removed to avoid build issues - using custom styling instead
+import { Badge } from '@/components/ui/badge';
+import { Upload, X, Eye, Loader2 } from 'lucide-react';
+import { cn } from '@/lib/utils';
interface UppyPhotoUploadProps {
onUploadComplete?: (urls: string[]) => void;
@@ -23,6 +23,8 @@ interface UppyPhotoUploadProps {
className?: string;
children?: React.ReactNode;
disabled?: boolean;
+ showPreview?: boolean;
+ size?: 'default' | 'compact' | 'large';
}
interface CloudflareResponse {
@@ -55,9 +57,13 @@ export function UppyPhotoUpload({
className = '',
children,
disabled = false,
+ showPreview = true,
+ size = 'default',
}: UppyPhotoUploadProps) {
const [isModalOpen, setIsModalOpen] = useState(false);
const [uploadedImages, setUploadedImages] = useState
([]);
+ const [isUploading, setIsUploading] = useState(false);
+ const [uploadProgress, setUploadProgress] = useState(0);
const uppyRef = useRef(null);
const { toast } = useToast();
@@ -81,11 +87,18 @@ export function UppyPhotoUpload({
showRemoveButtonAfterComplete: true,
showSelectedFiles: true,
note: `Images up to ${maxSizeMB}MB, max ${maxFiles} files`,
+ theme: 'auto',
+ closeAfterFinish: true,
});
// Add Image Editor plugin
uppy.use(ImageEditor, {
quality: 0.8,
+ cropperOptions: {
+ viewMode: 1,
+ background: false,
+ autoCropArea: 1,
+ },
});
// Add XHR Upload plugin (configured per file in beforeUpload)
@@ -103,6 +116,8 @@ export function UppyPhotoUpload({
return;
}
+ setIsUploading(true);
+ setUploadProgress(0);
onUploadStart?.();
// Process each file to get upload URL
@@ -144,6 +159,11 @@ export function UppyPhotoUpload({
}
});
+ // Handle upload progress
+ uppy.on('progress', (progress) => {
+ setUploadProgress(Math.round(progress));
+ });
+
// Handle upload success
uppy.on('upload-success', async (file, response) => {
try {
@@ -187,6 +207,7 @@ export function UppyPhotoUpload({
// Handle upload error
uppy.on('upload-error', (file, error, response) => {
console.error('Upload error:', error);
+ setIsUploading(false);
// Check if it's an expired URL error and retry
if (error.message?.includes('expired') || response?.status === 400) {
@@ -219,6 +240,9 @@ export function UppyPhotoUpload({
// Handle upload complete
uppy.on('complete', (result) => {
+ setIsUploading(false);
+ setUploadProgress(0);
+
if (result.successful.length > 0) {
onUploadComplete?.(uploadedImages);
toast({
@@ -234,7 +258,15 @@ export function UppyPhotoUpload({
return () => {
uppy.destroy();
};
- }, [maxFiles, maxSizeMB, allowedFileTypes, metadata, variant, disabled, onUploadStart, onUploadComplete, onUploadError, toast, uploadedImages]);
+ }, [maxFiles, maxSizeMB, allowedFileTypes, metadata, variant, disabled, onUploadStart, onUploadComplete, onUploadError, toast, uploadedImages, size]);
+
+ const removeImage = (index: number) => {
+ const newUrls = uploadedImages.filter((_, i) => i !== index);
+ setUploadedImages(newUrls);
+ if (onUploadComplete) {
+ onUploadComplete(newUrls);
+ }
+ };
const handleOpenModal = () => {
if (!disabled) {
@@ -242,26 +274,101 @@ export function UppyPhotoUpload({
}
};
- return (
- <>
-
- {children ? (
-
- {children}
-
- ) : (
-
-
- Upload Photos
-
- )}
-
+ const getSizeClasses = () => {
+ switch (size) {
+ case 'compact':
+ return 'px-4 py-2 text-sm';
+ case 'large':
+ return 'px-8 py-4 text-lg';
+ default:
+ return 'px-6 py-3';
+ }
+ };
+ const renderUploadTrigger = () => {
+ if (children) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ const baseClasses = "photo-upload-trigger transition-all duration-300 flex items-center justify-center gap-2";
+ const sizeClasses = getSizeClasses();
+ const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer hover:scale-105';
+
+ return (
+
+ {isUploading ? (
+ <>
+
+ Uploading... {uploadProgress}%
+ >
+ ) : (
+ <>
+
+ {size === 'compact' ? 'Upload' : 'Upload Photos'}
+ >
+ )}
+
+ );
+ };
+
+ const renderPreview = () => {
+ if (!showPreview || uploadedImages.length === 0) return null;
+
+ return (
+
+
+
+ Uploaded Photos ({uploadedImages.length})
+
+
+ {uploadedImages.length}/{maxFiles}
+
+
+
+ {uploadedImages.map((url, index) => (
+
+
+
+ removeImage(index)}
+ className="p-1 bg-destructive text-destructive-foreground rounded-full hover:bg-destructive/90 transition-colors mr-2"
+ title="Remove image"
+ >
+
+
+ window.open(url, '_blank')}
+ className="p-1 bg-primary text-primary-foreground rounded-full hover:bg-primary/90 transition-colors"
+ title="View full size"
+ >
+
+
+
+
+ ))}
+
+
+ );
+ };
+
+ return (
+
+ {renderUploadTrigger()}
+ {renderPreview()}
+
{uppyRef.current && (
)}
-
- {uploadedImages.length > 0 && (
-
- {uploadedImages.map((url, index) => (
-
-
-
-
- ))}
-
- )}
- >
+
);
}
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
index 2b4e6184..4bffd05f 100644
--- a/src/index.css
+++ b/src/index.css
@@ -119,3 +119,203 @@ All colors MUST be HSL.
@apply bg-background text-foreground;
}
}
+
+/* Custom Uppy Photo Upload Styling */
+@layer components {
+ /* Uppy Dashboard customization to match theme */
+ .uppy-Dashboard {
+ @apply bg-card text-card-foreground border border-border rounded-lg;
+ box-shadow: var(--shadow-card);
+ }
+
+ .uppy-Dashboard-inner {
+ @apply bg-transparent;
+ }
+
+ .uppy-Dashboard-dropFilesHereHint {
+ @apply text-muted-foreground;
+ }
+
+ .uppy-Dashboard-browse {
+ @apply text-primary hover:text-primary-glow transition-colors duration-300;
+ }
+
+ .uppy-Dashboard-AddFiles {
+ @apply bg-gradient-to-r from-primary to-secondary text-primary-foreground;
+ background: var(--gradient-primary);
+ border: none;
+ border-radius: var(--radius);
+ transition: var(--transition-smooth);
+ }
+
+ .uppy-Dashboard-AddFiles:hover {
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-glow);
+ }
+
+ .uppy-Dashboard-AddFiles-info {
+ @apply text-primary-foreground;
+ }
+
+ .uppy-Dashboard-Item {
+ @apply bg-card border border-border rounded-lg;
+ }
+
+ .uppy-Dashboard-Item-preview {
+ @apply rounded-lg overflow-hidden;
+ }
+
+ .uppy-Dashboard-Item-progress {
+ @apply bg-muted;
+ }
+
+ .uppy-Dashboard-Item-progressIndicator {
+ @apply bg-primary;
+ }
+
+ .uppy-Dashboard-Item-action--remove {
+ @apply text-destructive hover:text-destructive-foreground hover:bg-destructive;
+ transition: var(--transition-smooth);
+ }
+
+ .uppy-Dashboard-Item-action--edit {
+ @apply text-accent hover:text-accent-foreground hover:bg-accent;
+ transition: var(--transition-smooth);
+ }
+
+ /* Modal overlay styling */
+ .uppy-Dashboard--modal {
+ z-index: 9999;
+ }
+
+ .uppy-Dashboard--modal .uppy-Dashboard-overlay {
+ @apply bg-background/80 backdrop-blur-sm;
+ }
+
+ .uppy-Dashboard--modal .uppy-Dashboard-inner {
+ @apply bg-card border border-border rounded-xl;
+ box-shadow: var(--shadow-intense);
+ max-width: 90vw;
+ max-height: 90vh;
+ }
+
+ /* Status bar customization */
+ .uppy-StatusBar {
+ @apply bg-muted border-t border-border;
+ }
+
+ .uppy-StatusBar-progress {
+ @apply bg-primary;
+ }
+
+ .uppy-StatusBar-actions {
+ @apply gap-2;
+ }
+
+ .uppy-StatusBar-actionBtn {
+ @apply bg-primary text-primary-foreground hover:bg-primary/90;
+ border: none;
+ border-radius: var(--radius);
+ transition: var(--transition-smooth);
+ }
+
+ .uppy-StatusBar-actionBtn:hover {
+ transform: translateY(-1px);
+ }
+
+ /* Image editor customization */
+ .uppy-ImageEditor {
+ @apply bg-card text-card-foreground;
+ }
+
+ .uppy-ImageEditor-controls {
+ @apply bg-muted border-t border-border;
+ }
+
+ .uppy-ImageEditor-controls button {
+ @apply text-foreground hover:text-primary;
+ transition: var(--transition-smooth);
+ }
+
+ /* Custom upload trigger styling */
+ .photo-upload-trigger {
+ @apply relative overflow-hidden;
+ background: var(--gradient-primary);
+ transition: var(--transition-smooth);
+ }
+
+ .photo-upload-trigger:hover {
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-glow);
+ }
+
+ .photo-upload-trigger::before {
+ content: '';
+ @apply absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent;
+ transform: translateX(-100%);
+ transition: transform 0.6s ease;
+ }
+
+ .photo-upload-trigger:hover::before {
+ transform: translateX(100%);
+ }
+
+ /* Upload preview grid */
+ .upload-preview-grid {
+ @apply grid gap-4;
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ }
+
+ .upload-preview-item {
+ @apply relative aspect-square rounded-lg overflow-hidden border border-border;
+ background: var(--gradient-card);
+ transition: var(--transition-smooth);
+ }
+
+ .upload-preview-item:hover {
+ transform: scale(1.05);
+ box-shadow: var(--shadow-card);
+ }
+
+ .upload-preview-item img {
+ @apply w-full h-full object-cover;
+ }
+
+ .upload-preview-overlay {
+ @apply absolute inset-0 bg-black/40 opacity-0 hover:opacity-100 flex items-center justify-center;
+ transition: var(--transition-smooth);
+ }
+
+ /* Loading states */
+ .upload-loading {
+ @apply relative;
+ }
+
+ .upload-loading::after {
+ content: '';
+ @apply absolute inset-0 bg-primary/10 rounded-lg;
+ animation: pulse 2s infinite;
+ }
+
+ /* Mobile optimizations */
+ @media (max-width: 640px) {
+ .uppy-Dashboard--modal .uppy-Dashboard-inner {
+ @apply m-4 max-h-[80vh];
+ }
+
+ .upload-preview-grid {
+ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
+ @apply gap-2;
+ }
+ }
+
+ /* Dark mode specific adjustments */
+ .dark .uppy-Dashboard {
+ border-color: hsl(var(--border));
+ }
+
+ .dark .uppy-Dashboard-Item {
+ background-color: hsl(var(--card));
+ border-color: hsl(var(--border));
+ }
+}