mirror of
https://github.com/Such-Software/smirk-website.git
synced 2026-10-17 05:51:28 +02:00
feat: add public tip claiming UI
- Extract fragment key from URL - Check confirmations before allowing claim - Add claim button with loading/error states - Update SmirkAPI types for claimPublicTip
This commit is contained in:
+123
-2
@@ -29,6 +29,10 @@ export default function TipPage() {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [hasExtension, setHasExtension] = useState<boolean | null>(null);
|
const [hasExtension, setHasExtension] = useState<boolean | null>(null);
|
||||||
|
const [fragmentKey, setFragmentKey] = useState<string | null>(null);
|
||||||
|
const [claiming, setClaiming] = useState(false);
|
||||||
|
const [claimError, setClaimError] = useState<string | null>(null);
|
||||||
|
const [claimSuccess, setClaimSuccess] = useState<{ txid: string } | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check for extension
|
// Check for extension
|
||||||
@@ -38,6 +42,14 @@ export default function TipPage() {
|
|||||||
return () => clearTimeout(timeout);
|
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(() => {
|
useEffect(() => {
|
||||||
if (!tipId) return;
|
if (!tipId) return;
|
||||||
|
|
||||||
@@ -57,6 +69,44 @@ export default function TipPage() {
|
|||||||
loadTip();
|
loadTip();
|
||||||
}, [tipId]);
|
}, [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;
|
const coin = tip ? COINS[tip.asset.toLowerCase()] : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -150,8 +200,79 @@ export default function TipPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Claim instructions */}
|
{/* Claim success */}
|
||||||
{tip.status === 'pending' && (
|
{claimSuccess && (
|
||||||
|
<div className="bg-green-500/10 border border-green-500/50 rounded-xl p-4">
|
||||||
|
<h3 className="text-green-400 font-semibold mb-2">Claimed!</h3>
|
||||||
|
<p className="text-zinc-400 text-sm">
|
||||||
|
Funds are being sent to your wallet.
|
||||||
|
</p>
|
||||||
|
<p className="text-zinc-500 text-xs mt-2 font-mono break-all">
|
||||||
|
TX: {claimSuccess.txid}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Needs confirmations */}
|
||||||
|
{needsConfirmations && !claimSuccess && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h2 className="text-xl font-semibold text-[#fbeb0a]">Waiting for Confirmations</h2>
|
||||||
|
<div className="text-zinc-400 text-sm">
|
||||||
|
<p>This tip is still confirming on the blockchain.</p>
|
||||||
|
<p className="mt-2">
|
||||||
|
<span className="text-[#fbeb0a] font-semibold">{tip?.funding_confirmations ?? 0}</span>
|
||||||
|
{' / '}
|
||||||
|
<span>{tip?.confirmations_required ?? 0}</span>
|
||||||
|
{' confirmations'}
|
||||||
|
</p>
|
||||||
|
<p className="mt-3 text-zinc-500 text-xs">
|
||||||
|
Refresh this page to check progress.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Claim button for public tips */}
|
||||||
|
{isClaimable && !claimSuccess && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h2 className="text-xl font-semibold">
|
||||||
|
{hasExtension ? 'Ready to Claim!' : 'Install Smirk Wallet'}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{hasExtension ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<button
|
||||||
|
onClick={handleClaim}
|
||||||
|
disabled={claiming}
|
||||||
|
className="px-8 py-4 bg-[#fbeb0a] text-black font-bold rounded-xl
|
||||||
|
hover:bg-[#d4c708] transition-colors disabled:opacity-50
|
||||||
|
disabled:cursor-not-allowed text-lg w-full"
|
||||||
|
>
|
||||||
|
{claiming ? 'Claiming...' : 'Claim This Tip'}
|
||||||
|
</button>
|
||||||
|
{claimError && (
|
||||||
|
<p className="text-red-400 text-sm">{claimError}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-zinc-400 text-sm space-y-4">
|
||||||
|
<p>You need the Smirk Wallet extension to claim tips.</p>
|
||||||
|
<a
|
||||||
|
href="https://chrome.google.com/webstore/detail/smirk-wallet"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-block px-6 py-3 bg-[#fbeb0a] text-black font-bold rounded-xl
|
||||||
|
hover:bg-[#d4c708] transition-colors"
|
||||||
|
>
|
||||||
|
Install Smirk Wallet
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Targeted tip instructions (no fragment key) */}
|
||||||
|
{tip.status === 'pending' && !fragmentKey && !tip.is_public && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h2 className="text-xl font-semibold">
|
<h2 className="text-xl font-semibold">
|
||||||
{hasExtension ? 'Claim This Tip' : 'Install Smirk Wallet'}
|
{hasExtension ? 'Claim This Tip' : 'Install Smirk Wallet'}
|
||||||
|
|||||||
@@ -154,6 +154,8 @@ export interface PublicTipInfo {
|
|||||||
status: string;
|
status: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
is_public: boolean;
|
is_public: boolean;
|
||||||
|
funding_confirmations: number;
|
||||||
|
confirmations_required: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPublicTipInfo(tipId: string): Promise<PublicTipInfo> {
|
export async function getPublicTipInfo(tipId: string): Promise<PublicTipInfo> {
|
||||||
|
|||||||
Vendored
+7
@@ -19,6 +19,12 @@ interface SmirkSignResult {
|
|||||||
signatures: SmirkSignature[];
|
signatures: SmirkSignature[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SmirkClaimResult {
|
||||||
|
success: boolean;
|
||||||
|
txid?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface SmirkAPI {
|
interface SmirkAPI {
|
||||||
isSmirk: boolean;
|
isSmirk: boolean;
|
||||||
connect(): Promise<SmirkPublicKeys>;
|
connect(): Promise<SmirkPublicKeys>;
|
||||||
@@ -26,6 +32,7 @@ interface SmirkAPI {
|
|||||||
isConnected(): Promise<boolean>;
|
isConnected(): Promise<boolean>;
|
||||||
getPublicKeys(): Promise<SmirkPublicKeys | null>;
|
getPublicKeys(): Promise<SmirkPublicKeys | null>;
|
||||||
signMessage(message: string): Promise<SmirkSignResult>;
|
signMessage(message: string): Promise<SmirkSignResult>;
|
||||||
|
claimPublicTip(tipId: string, fragmentKey: string): Promise<SmirkClaimResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
|
|||||||
Reference in New Issue
Block a user