| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- import { useState } from 'react';
- import { Link } from 'react-router-dom';
- 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';
- export default function HomePage() {
- const { isAuthenticated } = useAuth();
- const { uploadImage } = useImages(PUBLIC_BUCKET);
- const [isUploading, setIsUploading] = useState(false);
- const [uploadResult, setUploadResult] = useState<{
- success: boolean;
- url?: string;
- error?: string;
- } | null>(null);
- const handleUpload = async (file: File, options: UploadOptions) => {
- setIsUploading(true);
- setUploadResult(null);
- try {
- const image = await uploadImage(file, options);
- if (image) {
- // Use short URL if available, otherwise fall back to raw URL
- const shareUrl = image.shortUrl
- ? `${window.location.origin}${image.shortUrl}`
- : image.url;
- setUploadResult({ success: true, url: shareUrl });
- }
- } catch (err) {
- setUploadResult({
- success: false,
- error: err instanceof Error ? err.message : 'Upload failed',
- });
- } finally {
- setIsUploading(false);
- }
- };
- const copyToClipboard = async (url: string) => {
- try {
- await navigator.clipboard.writeText(url);
- alert('Link copied to clipboard!');
- } catch {
- // Fallback
- const input = document.createElement('input');
- input.value = url;
- document.body.appendChild(input);
- input.select();
- document.execCommand('copy');
- document.body.removeChild(input);
- alert('Link copied to clipboard!');
- }
- };
- return (
- <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
- {/* Hero section */}
- <div className="text-center mb-12">
- <h1
- className="text-4xl sm:text-5xl font-bold mb-4"
- style={{ color: 'var(--text-primary)' }}
- >
- Share images instantly
- </h1>
- <p
- className="text-xl mb-6"
- style={{ color: 'var(--text-secondary)' }}
- >
- Upload an image and get a shareable link. No account required.
- </p>
- {!isAuthenticated && (
- <p className="text-sm" style={{ color: 'var(--text-muted)' }}>
- <Link
- to="/register"
- className="font-medium"
- style={{ color: 'var(--accent-600)' }}
- >
- Create an account
- </Link>{' '}
- to manage your uploads and access more features.
- </p>
- )}
- </div>
- {/* Upload result */}
- {uploadResult && (
- <div
- className={`mb-8 p-6 rounded-xl border ${
- uploadResult.success
- ? 'bg-green-50 border-green-200 dark:bg-green-900/20 dark:border-green-800'
- : 'bg-red-50 border-red-200 dark:bg-red-900/20 dark:border-red-800'
- }`}
- >
- {uploadResult.success ? (
- <div className="text-center">
- <div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-green-100 dark:bg-green-900/30 mb-4">
- <svg className="h-6 w-6 text-green-600 dark:text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
- </svg>
- </div>
- <h3 className="text-lg font-semibold text-green-900 dark:text-green-300 mb-2">
- Image uploaded successfully!
- </h3>
- <div className="flex gap-2 max-w-xl mx-auto">
- <input
- type="text"
- value={uploadResult.url}
- readOnly
- className="input flex-1 text-sm"
- />
- <button
- onClick={() => uploadResult.url && copyToClipboard(uploadResult.url)}
- className="btn btn-primary whitespace-nowrap"
- >
- Copy Link
- </button>
- </div>
- <div className="mt-4">
- <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 →
- </a>
- </div>
- <button
- onClick={() => setUploadResult(null)}
- className="mt-4 text-sm transition-opacity hover:opacity-80"
- style={{ color: 'var(--text-secondary)' }}
- >
- Upload another image
- </button>
- </div>
- ) : (
- <div className="text-center">
- <div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/30 mb-4">
- <svg className="h-6 w-6 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
- </svg>
- </div>
- <h3 className="text-lg font-semibold text-red-900 dark:text-red-300 mb-2">
- Upload failed
- </h3>
- <p className="text-red-700 dark:text-red-400 mb-4">{uploadResult.error}</p>
- <button
- onClick={() => setUploadResult(null)}
- className="btn btn-secondary"
- >
- Try again
- </button>
- </div>
- )}
- </div>
- )}
- {/* Upload area */}
- {!uploadResult?.success && (
- <div className="card p-8">
- <ImageUploader
- onUpload={handleUpload}
- isUploading={isUploading}
- />
- </div>
- )}
- {/* Features */}
- <div className="mt-16 grid md:grid-cols-3 gap-8">
- <div className="text-center">
- <div
- className="inline-flex items-center justify-center w-12 h-12 rounded-full mb-4"
- style={{ backgroundColor: 'var(--accent-100)' }}
- >
- <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 10V3L4 14h7v7l9-11h-7z" />
- </svg>
- </div>
- <h3
- className="text-lg font-semibold mb-2"
- style={{ color: 'var(--text-primary)' }}
- >
- Instant Upload
- </h3>
- <p style={{ color: 'var(--text-secondary)' }}>
- Drop your image and get a link instantly. No sign-up needed.
- </p>
- </div>
- <div className="text-center">
- <div
- className="inline-flex items-center justify-center w-12 h-12 rounded-full mb-4"
- style={{ backgroundColor: 'var(--accent-100)' }}
- >
- <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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
- </svg>
- </div>
- <h3
- className="text-lg font-semibold mb-2"
- style={{ color: 'var(--text-primary)' }}
- >
- Auto Expiry
- </h3>
- <p style={{ color: 'var(--text-secondary)' }}>
- Set expiration times to automatically delete images after a period.
- </p>
- </div>
- <div className="text-center">
- <div
- className="inline-flex items-center justify-center w-12 h-12 rounded-full mb-4"
- style={{ backgroundColor: 'var(--accent-100)' }}
- >
- <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="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" />
- </svg>
- </div>
- <h3
- className="text-lg font-semibold mb-2"
- style={{ color: 'var(--text-primary)' }}
- >
- Easy Sharing
- </h3>
- <p style={{ color: 'var(--text-secondary)' }}>
- Get shareable links for your images with just one click.
- </p>
- </div>
- </div>
- </div>
- );
- }
|