The entire skate-spots codebase was accidentally copied into src/src/ and public/public/ instead of src/ and public/ directly. This left the real src/ directory as blank MKStack template stubs, explaining why the landing page showed the default template instead of SkateSpots. Changes made: - Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx, all pages, components, hooks, contexts, lib, i18n, test) - Copied all files from public/public/ up to public/ (images, manifest, etc.) - Deleted src/src/ and public/public/ nested directories - Removed HelloWorld.tsx template stub from pages/ - Retained the previously fixed index.css (Tailwind v3 directives) Full skate-spots feature set is now active: - All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.) - NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler - Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.) - Outfit Variable font, Amber pre-check in main.tsx - All hooks (useSkateSpots, useCoaches, useZaps, etc.) - i18n support (en/es) Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
167 lines
4.9 KiB
TypeScript
167 lines
4.9 KiB
TypeScript
import { useState, useCallback } from 'react';
|
|
import { isUnknownMintError, extractMintUrlFromError } from './useMintManager';
|
|
import type { Manager } from 'coco-cashu-core';
|
|
import type { Token } from '@cashu/cashu-ts';
|
|
import { useToast } from '@/hooks/useToast';
|
|
|
|
interface UseTokenOperationsProps {
|
|
coco: Manager | null;
|
|
activeMintUrl: string | null;
|
|
generateQRCode: (text: string) => Promise<string>;
|
|
getEncodedToken: (token: Token | string) => string;
|
|
}
|
|
|
|
export function useTokenOperations({
|
|
coco,
|
|
activeMintUrl,
|
|
generateQRCode,
|
|
getEncodedToken,
|
|
}: UseTokenOperationsProps) {
|
|
const { toast } = useToast();
|
|
|
|
// Receive state
|
|
const [tokenInput, setTokenInput] = useState('');
|
|
|
|
// Send state
|
|
const [sendAmount, setSendAmount] = useState('');
|
|
|
|
// Transaction output state
|
|
const [sentToken, setSentToken] = useState<string | null>(null);
|
|
const [sentTokenQRCode, setSentTokenQRCode] = useState<string>('');
|
|
|
|
// Generate QR code for sent token
|
|
const generateSentTokenQRCode = useCallback(async (token: string) => {
|
|
try {
|
|
const qrCodeUrl = await generateQRCode(token);
|
|
setSentTokenQRCode(qrCodeUrl);
|
|
} catch (err) {
|
|
console.error('[Cashu] Failed to generate sent token QR code:', err);
|
|
}
|
|
}, [generateQRCode]);
|
|
|
|
// Handle unknown mint error by adding the mint and retrying
|
|
const handleUnknownMintError = useCallback(
|
|
async (mintUrl: string) => {
|
|
if (!coco) return;
|
|
|
|
try {
|
|
console.log('[Cashu] Adding unknown mint:', mintUrl);
|
|
|
|
// Check if mint exists
|
|
const allMints = await coco.mint.getAllMints();
|
|
const isKnown = allMints.some(m => m.mintUrl.toLowerCase() === mintUrl.toLowerCase());
|
|
|
|
if (isKnown) {
|
|
// Mint exists but may not be trusted
|
|
const isTrusted = await coco.mint.isTrustedMint(mintUrl);
|
|
if (!isTrusted) {
|
|
await coco.mint.trustMint(mintUrl);
|
|
}
|
|
} else {
|
|
// Add the mint and automatically trust it
|
|
await coco.mint.addMint(mintUrl, { trusted: true });
|
|
}
|
|
|
|
// Retry receiving the token
|
|
await coco.wallet.receive(tokenInput);
|
|
setTokenInput('');
|
|
|
|
toast({
|
|
title: 'Token received',
|
|
description: `Token redeemed successfully! Added new mint: ${new URL(mintUrl).host}`
|
|
});
|
|
} catch (retryErr) {
|
|
toast({
|
|
variant: 'destructive',
|
|
title: 'Receive failed',
|
|
description: retryErr instanceof Error ? retryErr.message : 'Unknown error'
|
|
});
|
|
}
|
|
},
|
|
[coco, tokenInput, toast]
|
|
);
|
|
|
|
// Handle receive (token)
|
|
const handleTokenReceive = useCallback(async () => {
|
|
if (!coco || !tokenInput) return;
|
|
|
|
try {
|
|
const tokenToProcess = tokenInput.trim();
|
|
|
|
console.log('[Cashu] Attempting to receive token:', tokenToProcess.substring(0, 20) + '...');
|
|
await coco.wallet.receive(tokenToProcess);
|
|
setTokenInput('');
|
|
|
|
toast({
|
|
title: 'Token received',
|
|
description: 'Cashu token redeemed successfully!'
|
|
});
|
|
} catch (err) {
|
|
console.error('[Cashu] Token receive error:', err);
|
|
|
|
// Handle UnknownMintError by automatically adding the mint
|
|
if (isUnknownMintError(err)) {
|
|
const mintUrl = extractMintUrlFromError(err);
|
|
if (mintUrl) {
|
|
await handleUnknownMintError(mintUrl);
|
|
return;
|
|
}
|
|
}
|
|
|
|
toast({
|
|
variant: 'destructive',
|
|
title: 'Receive failed',
|
|
description: err instanceof Error ? err.message : 'Unknown error'
|
|
});
|
|
}
|
|
}, [coco, tokenInput, handleUnknownMintError, toast]);
|
|
|
|
// Handle send (token)
|
|
const handleTokenSend = useCallback(async () => {
|
|
if (!coco || !sendAmount || !activeMintUrl) return;
|
|
|
|
try {
|
|
const amount = parseInt(sendAmount);
|
|
if (isNaN(amount) || amount <= 0) {
|
|
toast({
|
|
variant: 'destructive',
|
|
title: 'Invalid amount',
|
|
description: 'Please enter a valid amount'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const token = await coco.wallet.send(activeMintUrl, amount);
|
|
const encodedToken = getEncodedToken(token);
|
|
setSentToken(encodedToken);
|
|
await generateSentTokenQRCode(encodedToken);
|
|
setSendAmount('');
|
|
|
|
toast({
|
|
title: 'Token created',
|
|
description: `${amount} sats token ready to share`
|
|
});
|
|
} catch (err) {
|
|
toast({
|
|
variant: 'destructive',
|
|
title: 'Send failed',
|
|
description: err instanceof Error ? err.message : 'Unknown error'
|
|
});
|
|
}
|
|
}, [coco, sendAmount, activeMintUrl, getEncodedToken, generateSentTokenQRCode, toast]);
|
|
|
|
return {
|
|
// Receive state
|
|
tokenInput,
|
|
setTokenInput,
|
|
handleTokenReceive,
|
|
// Send state
|
|
sendAmount,
|
|
setSendAmount,
|
|
handleTokenSend,
|
|
// Transaction output state
|
|
sentToken,
|
|
setSentToken,
|
|
sentTokenQRCode,
|
|
};
|
|
}
|