diff --git a/src/app/tip/[id]/page.tsx b/src/app/tip/[id]/page.tsx index a16c7f0..a520d9c 100644 --- a/src/app/tip/[id]/page.tsx +++ b/src/app/tip/[id]/page.tsx @@ -29,6 +29,10 @@ export default function TipPage() { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [hasExtension, setHasExtension] = useState(null); + const [fragmentKey, setFragmentKey] = useState(null); + const [claiming, setClaiming] = useState(false); + const [claimError, setClaimError] = useState(null); + const [claimSuccess, setClaimSuccess] = useState<{ txid: string } | null>(null); useEffect(() => { // Check for extension @@ -38,6 +42,14 @@ export default function TipPage() { return () => clearTimeout(timeout); }, []); + // Extract fragment key from URL + useEffect(() => { + const hash = window.location.hash; + if (hash && hash.length > 1) { + setFragmentKey(hash.slice(1)); // Remove the # prefix + } + }, []); + useEffect(() => { if (!tipId) return; @@ -57,6 +69,44 @@ export default function TipPage() { loadTip(); }, [tipId]); + const handleClaim = async () => { + if (!window.smirk || !fragmentKey || !tipId) return; + + setClaiming(true); + setClaimError(null); + + try { + // First connect to extension (if not already) + await window.smirk.connect(); + + // Claim the tip + const result = await window.smirk.claimPublicTip(tipId, fragmentKey); + + if (result.success && result.txid) { + setClaimSuccess({ txid: result.txid }); + // Refresh tip info to show claimed status + const tipInfo = await getPublicTipInfo(tipId); + setTip(tipInfo); + } else { + setClaimError(result.error || 'Failed to claim tip'); + } + } catch (err) { + setClaimError(err instanceof Error ? err.message : 'Failed to claim tip'); + } finally { + setClaiming(false); + } + }; + + const isClaimable = tip?.status === 'pending' && + tip.is_public && + fragmentKey && + (tip.funding_confirmations ?? 0) >= (tip.confirmations_required ?? 0); + + const needsConfirmations = tip?.status === 'pending' && + tip.is_public && + fragmentKey && + (tip.funding_confirmations ?? 0) < (tip.confirmations_required ?? 0); + const coin = tip ? COINS[tip.asset.toLowerCase()] : null; return ( @@ -150,8 +200,79 @@ export default function TipPage() { - {/* Claim instructions */} - {tip.status === 'pending' && ( + {/* Claim success */} + {claimSuccess && ( +
+

Claimed!

+

+ Funds are being sent to your wallet. +

+

+ TX: {claimSuccess.txid} +

+
+ )} + + {/* Needs confirmations */} + {needsConfirmations && !claimSuccess && ( +
+

Waiting for Confirmations

+
+

This tip is still confirming on the blockchain.

+

+ {tip?.funding_confirmations ?? 0} + {' / '} + {tip?.confirmations_required ?? 0} + {' confirmations'} +

+

+ Refresh this page to check progress. +

+
+
+ )} + + {/* Claim button for public tips */} + {isClaimable && !claimSuccess && ( +
+

+ {hasExtension ? 'Ready to Claim!' : 'Install Smirk Wallet'} +

+ + {hasExtension ? ( +
+ + {claimError && ( +

{claimError}

+ )} +
+ ) : ( +
+

You need the Smirk Wallet extension to claim tips.

+ + Install Smirk Wallet + +
+ )} +
+ )} + + {/* Targeted tip instructions (no fragment key) */} + {tip.status === 'pending' && !fragmentKey && !tip.is_public && (

{hasExtension ? 'Claim This Tip' : 'Install Smirk Wallet'} diff --git a/src/lib/api.ts b/src/lib/api.ts index 3aab09c..955e9a9 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -154,6 +154,8 @@ export interface PublicTipInfo { status: string; created_at: string; is_public: boolean; + funding_confirmations: number; + confirmations_required: number; } export async function getPublicTipInfo(tipId: string): Promise { diff --git a/src/types/smirk.d.ts b/src/types/smirk.d.ts index a93e16c..0f2bb2b 100644 --- a/src/types/smirk.d.ts +++ b/src/types/smirk.d.ts @@ -19,6 +19,12 @@ interface SmirkSignResult { signatures: SmirkSignature[]; } +interface SmirkClaimResult { + success: boolean; + txid?: string; + error?: string; +} + interface SmirkAPI { isSmirk: boolean; connect(): Promise; @@ -26,6 +32,7 @@ interface SmirkAPI { isConnected(): Promise; getPublicKeys(): Promise; signMessage(message: string): Promise; + claimPublicTip(tipId: string, fragmentKey: string): Promise; } interface Window {