HomePage.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import { useState } from 'react';
  2. import { Link } from 'react-router-dom';
  3. import { useAuth } from '@picobaas/client/react';
  4. import { useImages } from '../hooks/useImages';
  5. import { PUBLIC_BUCKET } from '../config';
  6. import ImageUploader from '../components/ImageUploader';
  7. export default function HomePage() {
  8. const { isAuthenticated } = useAuth();
  9. const { uploadImage } = useImages(PUBLIC_BUCKET);
  10. const [isUploading, setIsUploading] = useState(false);
  11. const [uploadResult, setUploadResult] = useState<{
  12. success: boolean;
  13. url?: string;
  14. error?: string;
  15. } | null>(null);
  16. const handleUpload = async (file: File, expirySeconds: number) => {
  17. setIsUploading(true);
  18. setUploadResult(null);
  19. try {
  20. const image = await uploadImage(file, expirySeconds);
  21. if (image) {
  22. setUploadResult({ success: true, url: image.url });
  23. }
  24. } catch (err) {
  25. setUploadResult({
  26. success: false,
  27. error: err instanceof Error ? err.message : 'Upload failed',
  28. });
  29. } finally {
  30. setIsUploading(false);
  31. }
  32. };
  33. const copyToClipboard = async (url: string) => {
  34. try {
  35. await navigator.clipboard.writeText(url);
  36. alert('Link copied to clipboard!');
  37. } catch {
  38. // Fallback
  39. const input = document.createElement('input');
  40. input.value = url;
  41. document.body.appendChild(input);
  42. input.select();
  43. document.execCommand('copy');
  44. document.body.removeChild(input);
  45. alert('Link copied to clipboard!');
  46. }
  47. };
  48. return (
  49. <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
  50. {/* Hero section */}
  51. <div className="text-center mb-12">
  52. <h1 className="text-4xl sm:text-5xl font-bold text-gray-900 mb-4">
  53. Share images instantly
  54. </h1>
  55. <p className="text-xl text-gray-600 mb-6">
  56. Upload an image and get a shareable link. No account required.
  57. </p>
  58. {!isAuthenticated && (
  59. <p className="text-sm text-gray-500">
  60. <Link to="/register" className="text-primary-600 hover:text-primary-700 font-medium">
  61. Create an account
  62. </Link>{' '}
  63. to manage your uploads and access more features.
  64. </p>
  65. )}
  66. </div>
  67. {/* Upload result */}
  68. {uploadResult && (
  69. <div
  70. className={`mb-8 p-6 rounded-xl ${
  71. uploadResult.success
  72. ? 'bg-green-50 border border-green-200'
  73. : 'bg-red-50 border border-red-200'
  74. }`}
  75. >
  76. {uploadResult.success ? (
  77. <div className="text-center">
  78. <div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-green-100 mb-4">
  79. <svg className="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  80. <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
  81. </svg>
  82. </div>
  83. <h3 className="text-lg font-semibold text-green-900 mb-2">
  84. Image uploaded successfully!
  85. </h3>
  86. <div className="flex gap-2 max-w-xl mx-auto">
  87. <input
  88. type="text"
  89. value={uploadResult.url}
  90. readOnly
  91. className="input flex-1 bg-white text-sm"
  92. />
  93. <button
  94. onClick={() => uploadResult.url && copyToClipboard(uploadResult.url)}
  95. className="btn btn-primary whitespace-nowrap"
  96. >
  97. Copy Link
  98. </button>
  99. </div>
  100. <div className="mt-4">
  101. <a
  102. href={uploadResult.url}
  103. target="_blank"
  104. rel="noopener noreferrer"
  105. className="text-green-700 hover:text-green-800 text-sm font-medium"
  106. >
  107. Open image in new tab →
  108. </a>
  109. </div>
  110. <button
  111. onClick={() => setUploadResult(null)}
  112. className="mt-4 text-sm text-gray-600 hover:text-gray-800"
  113. >
  114. Upload another image
  115. </button>
  116. </div>
  117. ) : (
  118. <div className="text-center">
  119. <div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-red-100 mb-4">
  120. <svg className="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  121. <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
  122. </svg>
  123. </div>
  124. <h3 className="text-lg font-semibold text-red-900 mb-2">
  125. Upload failed
  126. </h3>
  127. <p className="text-red-700 mb-4">{uploadResult.error}</p>
  128. <button
  129. onClick={() => setUploadResult(null)}
  130. className="btn btn-secondary"
  131. >
  132. Try again
  133. </button>
  134. </div>
  135. )}
  136. </div>
  137. )}
  138. {/* Upload area */}
  139. {!uploadResult?.success && (
  140. <div className="card p-8">
  141. <ImageUploader
  142. onUpload={handleUpload}
  143. isUploading={isUploading}
  144. />
  145. </div>
  146. )}
  147. {/* Features */}
  148. <div className="mt-16 grid md:grid-cols-3 gap-8">
  149. <div className="text-center">
  150. <div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-primary-100 mb-4">
  151. <svg className="h-6 w-6 text-primary-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  152. <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
  153. </svg>
  154. </div>
  155. <h3 className="text-lg font-semibold text-gray-900 mb-2">Instant Upload</h3>
  156. <p className="text-gray-600">
  157. Drop your image and get a link instantly. No sign-up needed.
  158. </p>
  159. </div>
  160. <div className="text-center">
  161. <div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-primary-100 mb-4">
  162. <svg className="h-6 w-6 text-primary-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  163. <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
  164. </svg>
  165. </div>
  166. <h3 className="text-lg font-semibold text-gray-900 mb-2">Auto Expiry</h3>
  167. <p className="text-gray-600">
  168. Set expiration times to automatically delete images after a period.
  169. </p>
  170. </div>
  171. <div className="text-center">
  172. <div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-primary-100 mb-4">
  173. <svg className="h-6 w-6 text-primary-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  174. <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" />
  175. </svg>
  176. </div>
  177. <h3 className="text-lg font-semibold text-gray-900 mb-2">Easy Sharing</h3>
  178. <p className="text-gray-600">
  179. Get shareable links for your images with just one click.
  180. </p>
  181. </div>
  182. </div>
  183. </div>
  184. );
  185. }