import { useEffect, useId, useRef, useState } from 'react'; import { composeShareImage, copyText, shareClipboardText, detectSharePayloadMode, downloadShareImage, openShareSheet, shareFile, type ShareChannel, type ShareCapability, type ShareMetadata, } from '../share'; export interface SharePanelTranslations { title: string; instagram: string; whatsapp: string; preparing: string; unavailable: string; downloadImage: string; downloaded: string; downloadedMessageCopied: string; downloadedCopyFailed: string; failed: string; } type ShareAction = 'prepare_asset' | 'open_share_sheet' | 'download_image' | 'copy_product_link'; type TelemetryOutcome = 'attempted' | 'asset_ready' | 'share_sheet_closed' | 'cancelled' | 'fallback_ready' | 'copied' | 'downloaded' | 'failed'; export interface ShareIntentProperties extends Record { channel: ShareChannel; action: ShareAction; capability: ShareCapability; outcome: TelemetryOutcome; productId: string; variantId?: string; runId?: string; } interface Props { initiallyExpanded?: boolean; resultImage: string; productUrl: string | null; productName: string; brandName?: string | null; productImage?: string | null; productId: string; variantId?: string | null; runId?: string; t: SharePanelTranslations; onIntent: (properties: ShareIntentProperties) => void; } function InstagramIcon() { return ( ); } function WhatsAppIcon() { return ( ); } function DownloadIcon() { return ( ); } function ShareIcon() { return ( ); } interface ShareOptionButtonProps { label: string; badge: string; icon: 'instagram' | 'whatsapp' | 'download'; disabled: boolean; busy: boolean; onClick: () => void; } function ShareOptionButton({ label, badge, icon, disabled, busy, onClick }: ShareOptionButtonProps) { return ( ); } export function SharePanel({ resultImage, productUrl, productName, brandName, productImage, productId, variantId, runId, t, onIntent, initiallyExpanded = false }: Props) { const menuId = useId(); const copyEpoch = useRef(0); const [expanded, setExpanded] = useState(initiallyExpanded); const [asset, setAsset] = useState(null); const [assetFailed, setAssetFailed] = useState(false); const [busy, setBusy] = useState(null); const [lastChannel, setLastChannel] = useState('instagram_feed'); const [toast, setToast] = useState<{ message: string } | null>(null); useEffect(() => { if (!toast) return; const timer = setTimeout(() => setToast(null), 2500); return () => clearTimeout(timer); }, [toast]); const metadata: ShareMetadata = { itemName: productName, productUrl, brandName, productImage }; useEffect(() => { let active = true; setAsset(null); setAssetFailed(false); setBusy(null); setToast(null); void (async () => { try { const card = await composeShareImage(resultImage, { itemName: productName, productUrl, brandName, productImage }); if (!active) return; setAsset(shareFile(card)); } catch { if (!active) return; setAssetFailed(true); } })(); return () => { active = false; copyEpoch.current += 1; }; }, [productImage, brandName, productName, productUrl, resultImage, t.preparing, t.unavailable]); const emit = (channel: ShareChannel, action: ShareAction, capability: ShareCapability, outcome: TelemetryOutcome) => { onIntent({ channel, action, capability, outcome, productId, ...(variantId ? { variantId } : {}), ...(runId ? { runId } : {}) }); }; const handleChannel = async (channel: ShareChannel) => { if (!asset) return; const epoch = ++copyEpoch.current; setBusy(channel); setLastChannel(channel); setToast(null); const showToast = (message: string) => { if (copyEpoch.current === epoch) setToast({ message }); }; try { const text = shareClipboardText(metadata, channel); const mode = detectSharePayloadMode(asset, metadata, navigator, channel); if (mode !== 'none') { // Start the best-effort clipboard write, but never await it before sharing. // The file is already prepared, so both calls remain in this tap's gesture. void copyText(text).then(copied => { emit(channel, 'copy_product_link', 'file_share', copied ? 'copied' : 'failed'); }); emit(channel, 'open_share_sheet', 'file_share', 'attempted'); const result = await openShareSheet(asset, metadata, navigator, channel, channel === 'instagram_feed'); emit(channel, 'open_share_sheet', result.capability, result.outcome); // Cancellation and successful sheet closure are silent. A native error // must not cause an unexpected download when file sharing is available. if (result.outcome === 'failed') showToast(t.failed); } else { downloadShareImage(asset); emit(channel, 'download_image', 'download_only', 'downloaded'); const copied = await copyText(text); emit(channel, 'copy_product_link', copied ? 'download_clipboard' : 'download_only', copied ? 'copied' : 'failed'); showToast(copied ? t.downloadedMessageCopied : t.downloadedCopyFailed); } } catch { emit(channel, 'download_image', 'unavailable', 'failed'); showToast(t.failed); } finally { setBusy(null); } }; const handleDownload = () => { if (!asset) return; try { downloadShareImage(asset); emit(lastChannel, 'download_image', 'download_only', 'downloaded'); setToast({ message: t.downloaded }); } catch { emit(lastChannel, 'download_image', 'unavailable', 'failed'); setToast({ message: t.failed }); } }; const disabled = !asset || assetFailed || busy !== null; return (
{expanded ? (
void handleChannel('instagram_feed')} /> void handleChannel('whatsapp_message')} />
{!asset ?

{assetFailed ? t.unavailable : t.preparing}

: null}
) : null} {toast ?

{toast.message}

: null}
); }