export interface ShareOptions { cardName: string; cardImageUrl?: string; publicImageUrl?: string; // For social media previews onImageCopied?: (platform: string) => void; onImageUploaded?: (url: string) => void; } import { uploadImageToStorage } from './storageUtils'; // Cache for uploaded image URLs to avoid re-uploading the same image const uploadedImageCache = new Map(); // Helper function to get a cache key for an image const getImageCacheKey = (cardName: string, imageDataUrl: string): string => { // Use first 100 characters of base64 data as a simple hash const imageHash = imageDataUrl.slice(0, 100); return `${cardName}_${imageHash}`; }; // Helper function to upload image and get public URL with caching const getOrUploadImageUrl = async ( cardName: string, cardImageUrl: string, onImageUploaded?: (url: string) => void ): Promise => { try { // If the image URL is already a Firebase Storage URL or HTTP URL, return it directly if (cardImageUrl.startsWith('http')) { console.log('Using existing Firebase Storage URL:', cardImageUrl); return cardImageUrl; } // Check cache first for base64 images const cacheKey = getImageCacheKey(cardName, cardImageUrl); const cachedUrl = uploadedImageCache.get(cacheKey); if (cachedUrl) { console.log('Using cached image URL:', cachedUrl); return cachedUrl; } // Upload image to storage (for base64 data URLs) const imageUrl = await uploadImageToStorage(cardImageUrl, cardName); // Cache the URL uploadedImageCache.set(cacheKey, imageUrl); // Notify callback onImageUploaded?.(imageUrl); return imageUrl; } catch (error) { console.error('Failed to upload image:', error); return null; } }; const downloadImageAsBlob = async (imageDataUrl: string): Promise => { const response = await fetch(imageDataUrl); return response.blob(); }; // Helper function to copy image and text to clipboard export const copyImageAndText = async ({ cardName, cardImageUrl }: ShareOptions) => { try { const text = `Check out this AI-generated Pokémon card by Cardex: https://cardex.xavidop.me`; if (cardImageUrl && cardImageUrl.startsWith('data:') && navigator.clipboard?.write) { // Copy both image and text to clipboard (modern browsers) const imageBlob = await downloadImageAsBlob(cardImageUrl); const clipboardItems = [ new ClipboardItem({ 'image/png': imageBlob, 'text/plain': new Blob([text], { type: 'text/plain' }) }) ]; await navigator.clipboard.write(clipboardItems); return true; } else { // Fallback to text only await navigator.clipboard.writeText(text); return false; // Indicates image wasn't copied } } catch (error) { console.error('Failed to copy to clipboard:', error); throw error; } }; export const shareOnTwitter = async ({ cardName, cardImageUrl, publicImageUrl, onImageCopied, onImageUploaded }: ShareOptions) => { let shareText = `Check out this AI-generated Pokémon card by Cardex: https://cardex.xavidop.me`; // If we have an image, try to get or upload it to get a public URL let imageUrl = publicImageUrl; if (!imageUrl && cardImageUrl) { if (cardImageUrl.startsWith('http')) { // Already a Firebase Storage URL, use directly imageUrl = cardImageUrl; } else if (cardImageUrl.startsWith('data:')) { // Base64 data URL, need to upload const uploadedUrl = await getOrUploadImageUrl(cardName, cardImageUrl, onImageUploaded); if (uploadedUrl) { imageUrl = uploadedUrl; } else { // If upload failed, fallback to copying to clipboard try { await copyImageAndText({ cardName, cardImageUrl }); onImageCopied?.('Twitter/X'); } catch (clipboardError) { console.warn('Failed to copy image to clipboard:', clipboardError); } } } } // If we have a public image URL, include it in the tweet if (imageUrl) { shareText += ` ${imageUrl}`; } const url = `https://twitter.com/intent/tweet?text=${encodeURIComponent(shareText)}`; window.open(url, '_blank', 'width=550,height=420'); }; export const shareOnFacebook = async ({ cardName, cardImageUrl, publicImageUrl, onImageCopied, onImageUploaded }: ShareOptions) => { let shareText = `Check out this AI-generated Pokémon card by Cardex: https://cardex.xavidop.me`; let shareUrl = 'https://cardex.xavidop.me'; // If we have an image, try to get or upload it to get a public URL let imageUrl = publicImageUrl; if (!imageUrl && cardImageUrl) { if (cardImageUrl.startsWith('http')) { // Already a Firebase Storage URL, use directly imageUrl = cardImageUrl; shareUrl = imageUrl; // Use the image URL as the main share URL for better preview } else if (cardImageUrl.startsWith('data:')) { // Base64 data URL, need to upload const uploadedUrl = await getOrUploadImageUrl(cardName, cardImageUrl, onImageUploaded); if (uploadedUrl) { imageUrl = uploadedUrl; shareUrl = imageUrl; // Use the image URL as the main share URL for better preview } else { // If upload failed, fallback to copying to clipboard try { await copyImageAndText({ cardName, cardImageUrl }); onImageCopied?.('Facebook'); } catch (clipboardError) { console.warn('Failed to copy image to clipboard:', clipboardError); } } } } else if (imageUrl) { shareUrl = imageUrl; } const url = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}"e=${encodeURIComponent(shareText)}`; window.open(url, '_blank', 'width=580,height=296'); }; export const shareOnLinkedIn = async ({ cardName, cardImageUrl, publicImageUrl, onImageCopied, onImageUploaded }: ShareOptions) => { let shareText = `Check out this AI-generated Pokémon card by Cardex: https://cardex.xavidop.me`; let shareUrl = 'https://cardex.xavidop.me'; // If we have an image, try to get or upload it to get a public URL let imageUrl = publicImageUrl; if (!imageUrl && cardImageUrl) { if (cardImageUrl.startsWith('http')) { // Already a Firebase Storage URL, use directly imageUrl = cardImageUrl; shareUrl = imageUrl; // Use the image URL as the main share URL for better preview } else if (cardImageUrl.startsWith('data:')) { // Base64 data URL, need to upload const uploadedUrl = await getOrUploadImageUrl(cardName, cardImageUrl, onImageUploaded); if (uploadedUrl) { imageUrl = uploadedUrl; shareUrl = imageUrl; // Use the image URL as the main share URL for better preview } else { // If upload failed, fallback to copying to clipboard try { await copyImageAndText({ cardName, cardImageUrl }); onImageCopied?.('LinkedIn'); } catch (clipboardError) { console.warn('Failed to copy image to clipboard:', clipboardError); } } } } else if (imageUrl) { shareUrl = imageUrl; } const url = `https://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(shareUrl)}&title=${encodeURIComponent(`AI-Generated Pokémon Card: ${cardName}`)}&summary=${encodeURIComponent(shareText)}&source=Cardex`; window.open(url, '_blank', 'width=520,height=570'); }; export const copyShareLink = ({ cardName }: ShareOptions) => { const text = `Check out this AI-generated Pokémon card by Cardex: https://cardex.xavidop.me`; navigator.clipboard.writeText(text); }; // Native share API for mobile devices export const nativeShare = async ({ cardName, cardImageUrl }: ShareOptions) => { if (navigator.share) { try { const shareData: ShareData = { title: `AI-Generated Pokémon Card`, text: `Check out this AI-generated Pokémon card by Cardex: https://cardex.xavidop.me`, url: 'https://cardex.xavidop.me', }; // If we have an image and the browser supports file sharing if (cardImageUrl && navigator.canShare) { try { // Convert data URL or Firebase Storage URL to blob const response = await fetch(cardImageUrl); const blob = await response.blob(); const file = new File([blob], `${cardName}-card.png`, { type: 'image/png' }); // Check if we can share files if (navigator.canShare({ files: [file] })) { shareData.files = [file]; } } catch (error) { console.warn('Failed to add image to share:', error); } } await navigator.share(shareData); return true; } catch (error) { if ((error as Error).name !== 'AbortError') { console.error('Error sharing:', error); } return false; } } return false; };