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>
156 lines
4.3 KiB
TypeScript
156 lines
4.3 KiB
TypeScript
import { useState, useCallback, useRef } from 'react';
|
|
import type { Manager } from 'coco-cashu-core';
|
|
import { useToast } from '@/hooks/useToast';
|
|
|
|
interface UseLightningOperationsProps {
|
|
coco: Manager | null;
|
|
activeMintUrl: string | null;
|
|
generateQRCode: (text: string) => Promise<string>;
|
|
}
|
|
|
|
export function useLightningOperations({
|
|
coco,
|
|
activeMintUrl,
|
|
generateQRCode,
|
|
}: UseLightningOperationsProps) {
|
|
const { toast } = useToast();
|
|
|
|
// Receive state
|
|
const [quoteAmount, setQuoteAmount] = useState('');
|
|
const [showInvoiceModal, setShowInvoiceModal] = useState(false);
|
|
const [currentInvoice, setCurrentInvoice] = useState<{
|
|
quoteId: string;
|
|
mintUrl: string;
|
|
amount: number;
|
|
invoice: string;
|
|
qrCodeUrl: string;
|
|
expiry: number;
|
|
} | null>(null);
|
|
|
|
// Send state
|
|
const [lightningInvoice, setLightningInvoice] = useState('');
|
|
const [isProcessingPayment, setIsProcessingPayment] = useState(false);
|
|
|
|
const mintQuoteUnsubscribeRef = useRef<(() => void) | null>(null);
|
|
|
|
// Handle receive (create mint quote/invoice)
|
|
const handleLightningReceive = useCallback(async () => {
|
|
if (!coco || !quoteAmount || !activeMintUrl) return;
|
|
|
|
try {
|
|
const amount = parseInt(quoteAmount);
|
|
if (isNaN(amount) || amount <= 0) {
|
|
toast({
|
|
variant: 'destructive',
|
|
title: 'Invalid amount',
|
|
description: 'Please enter a valid amount'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const quote = await coco.quotes.createMintQuote(activeMintUrl, amount);
|
|
const qrCodeUrl = await generateQRCode(quote.request);
|
|
|
|
setCurrentInvoice({
|
|
quoteId: quote.quote,
|
|
mintUrl: activeMintUrl,
|
|
amount,
|
|
invoice: quote.request,
|
|
qrCodeUrl,
|
|
expiry: quote.expiry,
|
|
});
|
|
|
|
setShowInvoiceModal(true);
|
|
setQuoteAmount('');
|
|
|
|
// Clean up any previous listener
|
|
if (mintQuoteUnsubscribeRef.current) {
|
|
mintQuoteUnsubscribeRef.current();
|
|
mintQuoteUnsubscribeRef.current = null;
|
|
}
|
|
|
|
// Listen for payment
|
|
const quoteId = quote.quote;
|
|
const invoiceAmount = amount;
|
|
|
|
const unsubscribe = coco.on('mint-quote:redeemed', (payload: unknown) => {
|
|
const redeemedQuote = payload as { quoteId?: string };
|
|
|
|
if (redeemedQuote.quoteId === quoteId) {
|
|
toast({
|
|
title: 'Payment received',
|
|
description: `Successfully received ${invoiceAmount} sats!`
|
|
});
|
|
setShowInvoiceModal(false);
|
|
setCurrentInvoice(null);
|
|
mintQuoteUnsubscribeRef.current = null;
|
|
unsubscribe();
|
|
}
|
|
});
|
|
|
|
mintQuoteUnsubscribeRef.current = unsubscribe;
|
|
} catch (err) {
|
|
toast({
|
|
variant: 'destructive',
|
|
title: 'Failed to create invoice',
|
|
description: err instanceof Error ? err.message : 'Unknown error'
|
|
});
|
|
}
|
|
}, [coco, quoteAmount, activeMintUrl, generateQRCode, toast]);
|
|
|
|
// Handle send (pay Lightning invoice)
|
|
const handleLightningSend = useCallback(async () => {
|
|
if (isProcessingPayment || !coco || !lightningInvoice || !activeMintUrl) return;
|
|
|
|
setIsProcessingPayment(true);
|
|
|
|
try {
|
|
const invoice = lightningInvoice.trim();
|
|
|
|
// Create and pay melt quote
|
|
const meltQuote = await coco.quotes.createMeltQuote(activeMintUrl, invoice);
|
|
await coco.quotes.payMeltQuote(activeMintUrl, meltQuote.quote);
|
|
|
|
toast({
|
|
title: 'Payment sent',
|
|
description: 'Lightning payment completed successfully'
|
|
});
|
|
|
|
setLightningInvoice('');
|
|
} catch (err) {
|
|
toast({
|
|
variant: 'destructive',
|
|
title: 'Payment failed',
|
|
description: err instanceof Error ? err.message : 'Unknown error'
|
|
});
|
|
} finally {
|
|
setIsProcessingPayment(false);
|
|
}
|
|
}, [coco, lightningInvoice, activeMintUrl, isProcessingPayment, toast]);
|
|
|
|
// Cleanup listener on unmount
|
|
useCallback(() => {
|
|
return () => {
|
|
if (mintQuoteUnsubscribeRef.current) {
|
|
mintQuoteUnsubscribeRef.current();
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
return {
|
|
// Receive
|
|
quoteAmount,
|
|
setQuoteAmount,
|
|
showInvoiceModal,
|
|
setShowInvoiceModal,
|
|
currentInvoice,
|
|
setCurrentInvoice,
|
|
handleLightningReceive,
|
|
|
|
// Send
|
|
lightningInvoice,
|
|
setLightningInvoice,
|
|
isProcessingPayment,
|
|
handleLightningSend,
|
|
};
|
|
}
|