Просмотр исходного кода

feat(ui): add share link and delete button on upload success

- Display share URL prominently with improved copy button (shows copied state)
- Add delete button to remove uploaded image
- Show expiration warning with time remaining for expiring images
- Add "Register to keep your image" prompt for non-authenticated users
- Store full image data in upload result state for access to expiry info
Fszontagh 2 дней назад
Родитель
Сommit
d8247cbd6a
1 измененных файлов с 110 добавлено и 10 удалено
  1. 110 10
      src/pages/HomePage.tsx

+ 110 - 10
src/pages/HomePage.tsx

@@ -4,17 +4,21 @@ import { useAuth } from '@picobaas/client/react';
 import { useImages } from '../hooks/useImages';
 import { PUBLIC_BUCKET } from '../config';
 import ImageUploader from '../components/ImageUploader';
-import type { UploadOptions } from '../types';
+import { formatExpiryStatus } from '../utils/formatters';
+import type { ImageFile, UploadOptions } from '../types';
 
 export default function HomePage() {
   const { isAuthenticated } = useAuth();
-  const { uploadImage } = useImages(PUBLIC_BUCKET);
+  const { uploadImage, deleteImage } = useImages(PUBLIC_BUCKET);
   const [isUploading, setIsUploading] = useState(false);
+  const [isDeleting, setIsDeleting] = useState(false);
   const [uploadResult, setUploadResult] = useState<{
     success: boolean;
     url?: string;
+    image?: ImageFile;
     error?: string;
   } | null>(null);
+  const [copied, setCopied] = useState(false);
 
   const handleUpload = async (file: File, options: UploadOptions) => {
     setIsUploading(true);
@@ -27,7 +31,7 @@ export default function HomePage() {
         const shareUrl = image.shortUrl
           ? `${window.location.origin}${image.shortUrl}`
           : image.url;
-        setUploadResult({ success: true, url: shareUrl });
+        setUploadResult({ success: true, url: shareUrl, image });
       }
     } catch (err) {
       setUploadResult({
@@ -39,10 +43,25 @@ export default function HomePage() {
     }
   };
 
+  const handleDelete = async () => {
+    if (!uploadResult?.image) return;
+
+    setIsDeleting(true);
+    try {
+      await deleteImage(uploadResult.image);
+      setUploadResult(null);
+    } catch (err) {
+      alert(err instanceof Error ? err.message : 'Delete failed');
+    } finally {
+      setIsDeleting(false);
+    }
+  };
+
   const copyToClipboard = async (url: string) => {
     try {
       await navigator.clipboard.writeText(url);
-      alert('Link copied to clipboard!');
+      setCopied(true);
+      setTimeout(() => setCopied(false), 2000);
     } catch {
       // Fallback
       const input = document.createElement('input');
@@ -51,7 +70,8 @@ export default function HomePage() {
       input.select();
       document.execCommand('copy');
       document.body.removeChild(input);
-      alert('Link copied to clipboard!');
+      setCopied(true);
+      setTimeout(() => setCopied(false), 2000);
     }
   };
 
@@ -104,33 +124,113 @@ export default function HomePage() {
               <h3 className="text-lg font-semibold text-green-900 dark:text-green-300 mb-2">
                 Image uploaded successfully!
               </h3>
+
+              {/* Share URL with copy button */}
+              <p className="text-sm text-green-700 dark:text-green-400 mb-3">
+                Share this link with anyone:
+              </p>
               <div className="flex gap-2 max-w-xl mx-auto">
                 <input
                   type="text"
                   value={uploadResult.url}
                   readOnly
-                  className="input flex-1 text-sm"
+                  className="input flex-1 text-sm font-mono"
                 />
                 <button
                   onClick={() => uploadResult.url && copyToClipboard(uploadResult.url)}
                   className="btn btn-primary whitespace-nowrap"
                 >
-                  Copy Link
+                  {copied ? (
+                    <>
+                      <svg className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
+                      </svg>
+                      Copied!
+                    </>
+                  ) : (
+                    <>
+                      <svg className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
+                      </svg>
+                      Copy Link
+                    </>
+                  )}
                 </button>
               </div>
-              <div className="mt-4">
+
+              {/* Expiration warning */}
+              {uploadResult.image?.expiresAt && (
+                <div className="mt-4 p-3 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 max-w-xl mx-auto">
+                  <div className="flex items-center justify-center gap-2 text-amber-700 dark:text-amber-400">
+                    <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
+                    </svg>
+                    <span className="text-sm font-medium">
+                      {(() => {
+                        const status = formatExpiryStatus(uploadResult.image!.expiresAt!);
+                        return status.isExpired
+                          ? 'This image has expired'
+                          : `This image will be deleted in ${status.label.replace(' left', '')}`;
+                      })()}
+                    </span>
+                  </div>
+                </div>
+              )}
+
+              {/* Action buttons */}
+              <div className="mt-4 flex flex-col sm:flex-row items-center justify-center gap-3">
                 <a
                   href={uploadResult.url}
                   target="_blank"
                   rel="noopener noreferrer"
                   className="text-green-700 dark:text-green-400 hover:opacity-80 text-sm font-medium"
                 >
-                  Open image in new tab →
+                  Open image in new tab
                 </a>
+                <span className="hidden sm:inline text-gray-300 dark:text-gray-600">|</span>
+                <button
+                  onClick={handleDelete}
+                  disabled={isDeleting}
+                  className="text-red-600 dark:text-red-400 hover:opacity-80 text-sm font-medium disabled:opacity-50"
+                >
+                  {isDeleting ? 'Deleting...' : 'Delete image'}
+                </button>
               </div>
+
+              {/* Register prompt for non-authenticated users */}
+              {!isAuthenticated && (
+                <div className="mt-6 p-4 rounded-lg border max-w-xl mx-auto" style={{ backgroundColor: 'var(--surface-secondary)', borderColor: 'var(--border-color)' }}>
+                  <div className="flex items-start gap-3">
+                    <div className="flex-shrink-0">
+                      <svg className="h-6 w-6" style={{ color: 'var(--accent-600)' }} fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
+                      </svg>
+                    </div>
+                    <div className="text-left">
+                      <h4 className="text-sm font-semibold" style={{ color: 'var(--text-primary)' }}>
+                        Want to keep your image permanently?
+                      </h4>
+                      <p className="text-sm mt-1" style={{ color: 'var(--text-secondary)' }}>
+                        Create a free account to manage your uploads, remove expiration, and access more features.
+                      </p>
+                      <Link
+                        to="/register"
+                        className="inline-flex items-center gap-1 mt-2 text-sm font-medium"
+                        style={{ color: 'var(--accent-600)' }}
+                      >
+                        Create an account
+                        <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
+                        </svg>
+                      </Link>
+                    </div>
+                  </div>
+                </div>
+              )}
+
               <button
                 onClick={() => setUploadResult(null)}
-                className="mt-4 text-sm transition-opacity hover:opacity-80"
+                className="mt-6 text-sm transition-opacity hover:opacity-80"
                 style={{ color: 'var(--text-secondary)' }}
               >
                 Upload another image