Add payment method context to choose between native gorbagana and solana chain tokens
This commit is contained in:
parent
9995e76e87
commit
b47103bafa
@ -1,8 +1,8 @@
|
||||
# Client-side environment variables (must be prefixed with NEXT_PUBLIC_)
|
||||
|
||||
# Solana Token Payment Configuration
|
||||
# TODO: Use different RPC URL or use browser wallet
|
||||
# Solana Payment Configuration
|
||||
NEXT_PUBLIC_SOLANA_RPC_URL=https://skilled-prettiest-seed.solana-mainnet.quiknode.pro/eeecfebd04e345f69f1900cc3483cbbfea02a158
|
||||
NEXT_PUBLIC_SOLANA_SOL_RPC_URL=https://rpc.gorbagana.wtf
|
||||
NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS=71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFELvg
|
||||
|
||||
# Multisig address
|
||||
|
@ -15,7 +15,10 @@ import { PaymentMethod, SOL_PAYMENT_AMOUNT_LAMPORTS } from '@/constants/payments
|
||||
import { getRecipientAddress } from '@/services/solana';
|
||||
|
||||
assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required');
|
||||
assert(process.env.NEXT_PUBLIC_SOLANA_SOL_RPC_URL, 'SOLANA_SOL_RPC_URL is required');
|
||||
|
||||
const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL;
|
||||
const SOLANA_SOL_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_SOL_RPC_URL;
|
||||
|
||||
// Allow 20% slippage due to price fluctuations
|
||||
const ALLOWED_SLIPPAGE_FACTOR = 0.2
|
||||
@ -136,16 +139,19 @@ export const registryTransactionWithRetry = async (
|
||||
throw lastError;
|
||||
};
|
||||
|
||||
let connection: Connection;
|
||||
// Helper function to get the appropriate connection based on payment method
|
||||
const getConnection = (paymentMethod: PaymentMethod): Connection => {
|
||||
if (paymentMethod === 'sol' && SOLANA_SOL_RPC_URL) {
|
||||
return new Connection(SOLANA_SOL_RPC_URL);
|
||||
}
|
||||
return new Connection(SOLANA_RPC_URL);
|
||||
};
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
if (!connection) {
|
||||
connection = new Connection(SOLANA_RPC_URL);
|
||||
}
|
||||
|
||||
// First check if the request body is valid JSON
|
||||
let url, txHash, senderPublicKey, paymentMethod;
|
||||
let connection: Connection;
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
@ -153,6 +159,9 @@ export async function POST(request: NextRequest) {
|
||||
txHash = body.txHash;
|
||||
paymentMethod = body.paymentMethod as PaymentMethod;
|
||||
|
||||
// Get the appropriate connection based on payment method
|
||||
connection = getConnection(paymentMethod);
|
||||
|
||||
const tx = await connection.getParsedTransaction(txHash, 'confirmed');
|
||||
if (!tx) {
|
||||
console.error("Transaction not found.");
|
||||
|
@ -4,6 +4,7 @@ import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import ErrorBoundaryWrapper from "../components/ErrorBoundaryWrapper";
|
||||
import WalletProviders from "../components/WalletProviders";
|
||||
import { PaymentMethodProvider } from "../contexts/PaymentMethodContext";
|
||||
|
||||
|
||||
const geistSans = Geist({
|
||||
@ -32,9 +33,11 @@ export default function RootLayout({
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<ErrorBoundaryWrapper />
|
||||
<PaymentMethodProvider>
|
||||
<WalletProviders>
|
||||
{children}
|
||||
</WalletProviders>
|
||||
</PaymentMethodProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
122
src/app/page.tsx
122
src/app/page.tsx
@ -7,14 +7,17 @@ import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';
|
||||
import URLForm from '@/components/URLForm';
|
||||
import StatusDisplay from '@/components/StatusDisplay';
|
||||
import { createApplicationDeploymentRequest } from '@/services/registry';
|
||||
import { PaymentMethod } from '@/constants/payments';
|
||||
import { PaymentMethod, PAYMENT_METHOD_LABELS } from '@/constants/payments';
|
||||
import { useWallet } from '@solana/wallet-adapter-react';
|
||||
|
||||
import { usePaymentMethod } from '@/contexts/PaymentMethodContext';
|
||||
|
||||
// Dynamically import components to avoid SSR issues with browser APIs
|
||||
const PaymentModal = dynamic(() => import('@/components/PaymentModal'), { ssr: false });
|
||||
|
||||
export default function Home() {
|
||||
const { wallet, connected, publicKey } = useWallet();
|
||||
const { selectedPaymentMethod, setSelectedPaymentMethod } = usePaymentMethod();
|
||||
const [url, setUrl] = useState<string | null>(null);
|
||||
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
||||
const [status, setStatus] = useState<'idle' | 'creating' | 'success' | 'error'>('idle');
|
||||
@ -32,6 +35,20 @@ export default function Home() {
|
||||
setShowPaymentModal(true);
|
||||
};
|
||||
|
||||
// Helper function to check if current wallet is compatible with selected payment method
|
||||
const isWalletCompatible = () => {
|
||||
if (!selectedPaymentMethod || !wallet) return false;
|
||||
|
||||
const walletName = wallet.adapter.name.toLowerCase();
|
||||
const isBackpack = walletName.includes('backpack');
|
||||
|
||||
if (selectedPaymentMethod === 'sol') {
|
||||
return isBackpack; // Only Backpack for SOL
|
||||
} else {
|
||||
return !isBackpack; // Only non-Backpack wallets for SPL tokens
|
||||
}
|
||||
};
|
||||
|
||||
const handlePaymentComplete = useCallback(async (hash: string, paymentMethod: PaymentMethod) => {
|
||||
if (!publicKey || !url) {
|
||||
return
|
||||
@ -85,26 +102,97 @@ export default function Home() {
|
||||
Deploy Frontends with {process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} + Laconic
|
||||
</h1>
|
||||
|
||||
{/* Step 1: Payment Method Selection */}
|
||||
<div className="mb-10 p-6 rounded-lg" style={{ background: 'var(--muted-light)', borderLeft: '4px solid var(--primary)' }}>
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
||||
<span className="inline-flex items-center justify-center mr-3 w-7 h-7 rounded-full text-sm font-bold"
|
||||
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>1</span>
|
||||
Connect Your Wallet
|
||||
Choose Payment Method
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<button
|
||||
onClick={() => setSelectedPaymentMethod('sol')}
|
||||
className={`p-4 rounded-lg border-2 transition-colors ${
|
||||
selectedPaymentMethod === 'sol' ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-gray-400'
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor: selectedPaymentMethod === 'sol' ? 'var(--accent)' : 'var(--card-bg)',
|
||||
borderColor: selectedPaymentMethod === 'sol' ? 'var(--primary)' : 'var(--card-border)'
|
||||
}}
|
||||
>
|
||||
<div className="text-left">
|
||||
<h3 className="font-semibold text-lg mb-2">Native SOL</h3>
|
||||
<p className="text-sm" style={{ color: 'var(--muted-foreground)' }}>
|
||||
Pay 0.01 SOL (~$2-5 USD)
|
||||
</p>
|
||||
<p className="text-xs mt-1" style={{ color: 'var(--muted-foreground)' }}>
|
||||
Compatible with: Backpack
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSelectedPaymentMethod('spl-token')}
|
||||
className={`p-4 rounded-lg border-2 transition-colors ${
|
||||
selectedPaymentMethod === 'spl-token' ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-gray-400'
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor: selectedPaymentMethod === 'spl-token' ? 'var(--accent)' : 'var(--card-bg)',
|
||||
borderColor: selectedPaymentMethod === 'spl-token' ? 'var(--primary)' : 'var(--card-border)'
|
||||
}}
|
||||
>
|
||||
<div className="text-left">
|
||||
<h3 className="font-semibold text-lg mb-2">{process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} Token</h3>
|
||||
<p className="text-sm" style={{ color: 'var(--muted-foreground)' }}>
|
||||
Pay ${process.env.NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD} worth
|
||||
</p>
|
||||
<p className="text-xs mt-1" style={{ color: 'var(--muted-foreground)' }}>
|
||||
Compatible with: Phantom, Solflare
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 2: Wallet Connection */}
|
||||
<div className="mb-10 p-6 rounded-lg" style={{
|
||||
background: 'var(--muted-light)',
|
||||
borderLeft: '4px solid var(--primary)',
|
||||
opacity: selectedPaymentMethod ? '1' : '0.6'
|
||||
}}>
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
||||
<span className="inline-flex items-center justify-center mr-3 w-7 h-7 rounded-full text-sm font-bold"
|
||||
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>2</span>
|
||||
Connect Compatible Wallet
|
||||
</h2>
|
||||
{!selectedPaymentMethod ? (
|
||||
<p className="text-center" style={{ color: 'var(--muted-foreground)' }}>
|
||||
Please select a payment method first
|
||||
</p>
|
||||
) : (
|
||||
<div className="text-center">
|
||||
<p className="mb-4" style={{ color: 'var(--muted-foreground)' }}>
|
||||
Payment methods: <span className="font-semibold" style={{ color: 'var(--foreground)' }}>
|
||||
SOL or {process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} (Solana)
|
||||
Selected: <span className="font-semibold" style={{ color: 'var(--foreground)' }}>
|
||||
{PAYMENT_METHOD_LABELS[selectedPaymentMethod]}
|
||||
</span>
|
||||
</p>
|
||||
{connected && publicKey ? (
|
||||
<div className="flex flex-col items-center space-y-3">
|
||||
<div className="flex items-center">
|
||||
<span className="w-3 h-3 rounded-full mr-2" style={{ backgroundColor: 'var(--success)' }}></span>
|
||||
<p className="font-medium" style={{ color: 'var(--success)' }}>
|
||||
Connected ({wallet?.adapter.name})
|
||||
<span className="w-3 h-3 rounded-full mr-2" style={{
|
||||
backgroundColor: isWalletCompatible() ? 'var(--success)' : 'var(--destructive)'
|
||||
}}></span>
|
||||
<p className="font-medium" style={{
|
||||
color: isWalletCompatible() ? 'var(--success)' : 'var(--destructive)'
|
||||
}}>
|
||||
{isWalletCompatible() ? 'Compatible' : 'Incompatible'} Wallet ({wallet?.adapter.name})
|
||||
</p>
|
||||
</div>
|
||||
{!isWalletCompatible() && (
|
||||
<p className="text-sm" style={{ color: 'var(--destructive)' }}>
|
||||
This wallet is not compatible with {PAYMENT_METHOD_LABELS[selectedPaymentMethod]} payments.
|
||||
Please select a different wallet or payment method.
|
||||
</p>
|
||||
)}
|
||||
<div className="w-full p-3 rounded-md" style={{ background: 'var(--card-bg)', border: '1px solid var(--card-border)' }}>
|
||||
<p className="text-sm font-mono break-all text-center">{publicKey.toBase58()}</p>
|
||||
</div>
|
||||
@ -118,6 +206,13 @@ export default function Home() {
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<p className="mb-4 text-sm" style={{ color: 'var(--muted-foreground)' }}>
|
||||
{selectedPaymentMethod === 'sol'
|
||||
? 'Only Backpack wallet supports SOL payments'
|
||||
: 'Phantom and Solflare wallets support SPL token payments'
|
||||
}
|
||||
</p>
|
||||
<WalletMultiButton
|
||||
className="!px-6 !py-3 !rounded-md !w-full !transition-colors"
|
||||
style={{
|
||||
@ -125,22 +220,25 @@ export default function Home() {
|
||||
color: 'var(--primary-foreground)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Step 3: URL Input */}
|
||||
<div className="mb-8 p-6 rounded-lg" style={{
|
||||
background: 'var(--muted-light)',
|
||||
borderLeft: '4px solid var(--primary)',
|
||||
opacity: connected ? '1' : '0.6'
|
||||
opacity: (connected && isWalletCompatible()) ? '1' : '0.6'
|
||||
}}>
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
||||
<span className="inline-flex items-center justify-center mr-3 w-7 h-7 rounded-full text-sm font-bold"
|
||||
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>2</span>
|
||||
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>3</span>
|
||||
Enter URL to Deploy
|
||||
</h2>
|
||||
<URLForm
|
||||
onSubmit={handleUrlSubmit}
|
||||
disabled={!connected || status === 'creating'}
|
||||
disabled={!connected || !isWalletCompatible() || status === 'creating'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -148,7 +246,7 @@ export default function Home() {
|
||||
<div className="p-6 rounded-lg" style={{ background: 'var(--muted-light)', borderLeft: '4px solid var(--primary)' }}>
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
||||
<span className="inline-flex items-center justify-center mr-3 w-7 h-7 rounded-full text-sm font-bold"
|
||||
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>3</span>
|
||||
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>4</span>
|
||||
Deployment Status
|
||||
</h2>
|
||||
<StatusDisplay
|
||||
@ -166,7 +264,7 @@ export default function Home() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showPaymentModal && url && connected && publicKey && (
|
||||
{showPaymentModal && url && connected && publicKey && selectedPaymentMethod && (
|
||||
<PaymentModal
|
||||
isOpen={showPaymentModal}
|
||||
onClose={handleClosePaymentModal}
|
||||
|
@ -9,10 +9,12 @@ import { useWallet } from '@solana/wallet-adapter-react';
|
||||
import { sendSolanaPayment, getRecipientAddress } from '@/services/solana';
|
||||
import { getRequiredTokenInfo } from '@/services/jupiter-price';
|
||||
import { PaymentModalProps, PaymentRequest } from '@/types';
|
||||
import { PaymentMethod, PAYMENT_METHOD_LABELS, SOL_PAYMENT_AMOUNT_LAMPORTS } from '@/constants/payments';
|
||||
import { PAYMENT_METHOD_LABELS, SOL_PAYMENT_AMOUNT_LAMPORTS } from '@/constants/payments';
|
||||
import { usePaymentMethod } from '@/contexts/PaymentMethodContext';
|
||||
|
||||
assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required');
|
||||
const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL;
|
||||
const SOLANA_SOL_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_SOL_RPC_URL;
|
||||
|
||||
export default function PaymentModal({
|
||||
isOpen,
|
||||
@ -22,7 +24,8 @@ export default function PaymentModal({
|
||||
}: PaymentModalProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<PaymentMethod | null>(null);
|
||||
const { selectedPaymentMethod } = usePaymentMethod();
|
||||
const paymentMethod = selectedPaymentMethod;
|
||||
const [tokenAmount, setTokenAmount] = useState<number>(0);
|
||||
const [tokenDecimals, setTokenDecimals] = useState<number>(6); // Default fallback
|
||||
const [loadingPrice, setLoadingPrice] = useState(false);
|
||||
@ -30,6 +33,10 @@ export default function PaymentModal({
|
||||
const { wallet, publicKey } = useWallet();
|
||||
|
||||
const directConnection = useMemo(() => new Connection(SOLANA_RPC_URL), []);
|
||||
const solConnection = useMemo(() =>
|
||||
SOLANA_SOL_RPC_URL ? new Connection(SOLANA_SOL_RPC_URL) : directConnection,
|
||||
[directConnection]
|
||||
);
|
||||
|
||||
// Get configuration from environment variables
|
||||
const targetUsdAmount = parseFloat(process.env.NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD!);
|
||||
@ -38,7 +45,7 @@ export default function PaymentModal({
|
||||
|
||||
// Fetch token amount based on USD price when SPL token method is selected
|
||||
useEffect(() => {
|
||||
if (!isOpen || selectedPaymentMethod !== 'spl-token') {
|
||||
if (!isOpen || paymentMethod !== 'spl-token') {
|
||||
setLoadingPrice(false);
|
||||
return;
|
||||
}
|
||||
@ -60,35 +67,30 @@ export default function PaymentModal({
|
||||
};
|
||||
|
||||
fetchTokenAmount();
|
||||
}, [isOpen, selectedPaymentMethod, targetUsdAmount, mintAddress]);
|
||||
}, [isOpen, paymentMethod, targetUsdAmount, mintAddress]);
|
||||
|
||||
// Reset state when modal opens
|
||||
// Initialize payment method when modal opens
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setSelectedPaymentMethod(null);
|
||||
setError('');
|
||||
setTokenAmount(0);
|
||||
setLoadingPrice(false);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handlePaymentMethodChange = (method: PaymentMethod) => {
|
||||
setSelectedPaymentMethod(method);
|
||||
setError('');
|
||||
|
||||
// Set tokenAmount for SOL payments to maintain consistency
|
||||
if (method === 'sol') {
|
||||
if (paymentMethod === 'sol') {
|
||||
setTokenAmount(SOL_PAYMENT_AMOUNT_LAMPORTS);
|
||||
} else {
|
||||
setTokenAmount(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [isOpen, paymentMethod]);
|
||||
|
||||
|
||||
const handlePayment = useCallback(async () => {
|
||||
if (!selectedPaymentMethod) {
|
||||
setError('Please select a payment method.');
|
||||
if (!paymentMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tokenAmount === 0 || (selectedPaymentMethod === 'spl-token' && loadingPrice)) {
|
||||
if (tokenAmount === 0 || (paymentMethod === 'spl-token' && loadingPrice)) {
|
||||
setError('Payment amount not ready. Please wait.');
|
||||
return;
|
||||
}
|
||||
@ -103,20 +105,23 @@ export default function PaymentModal({
|
||||
|
||||
try {
|
||||
const paymentRequest: PaymentRequest = {
|
||||
paymentMethod: selectedPaymentMethod,
|
||||
paymentMethod: paymentMethod,
|
||||
amount: tokenAmount,
|
||||
recipientAddress: getRecipientAddress(selectedPaymentMethod)
|
||||
recipientAddress: getRecipientAddress(paymentMethod)
|
||||
};
|
||||
|
||||
// Use different RPC connection based on payment method
|
||||
const connectionToUse = paymentMethod === 'sol' ? solConnection : directConnection;
|
||||
|
||||
const result = await sendSolanaPayment(
|
||||
wallet.adapter,
|
||||
directConnection,
|
||||
connectionToUse,
|
||||
publicKey!.toBase58(),
|
||||
paymentRequest
|
||||
);
|
||||
|
||||
if (result.success && result.transactionSignature) {
|
||||
onPaymentComplete(result.transactionSignature, selectedPaymentMethod);
|
||||
onPaymentComplete(result.transactionSignature, paymentMethod);
|
||||
} else {
|
||||
setError(result.error || 'Payment failed. Please try again.');
|
||||
}
|
||||
@ -125,12 +130,10 @@ export default function PaymentModal({
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [selectedPaymentMethod, tokenAmount, loadingPrice, wallet, directConnection, publicKey, onPaymentComplete]);
|
||||
}, [paymentMethod, tokenAmount, loadingPrice, wallet, directConnection, solConnection, publicKey, onPaymentComplete]);
|
||||
|
||||
const getPaymentAmountDisplay = () => {
|
||||
if (!selectedPaymentMethod) return '';
|
||||
|
||||
switch (selectedPaymentMethod) {
|
||||
switch (paymentMethod) {
|
||||
case 'sol':
|
||||
return `${SOL_PAYMENT_AMOUNT_LAMPORTS / LAMPORTS_PER_SOL} SOL`;
|
||||
case 'spl-token':
|
||||
@ -141,7 +144,7 @@ export default function PaymentModal({
|
||||
}
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
if (!isOpen || !paymentMethod) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4 z-50" style={{ background: 'rgba(15, 23, 42, 0.75)' }}>
|
||||
@ -161,39 +164,23 @@ export default function PaymentModal({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Payment Method Selection */}
|
||||
{/* Payment Method Display */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-3" style={{ color: 'var(--foreground)' }}>
|
||||
Select Payment Method *
|
||||
Payment Method
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
{Object.entries(PAYMENT_METHOD_LABELS).map(([method, label]) => (
|
||||
<label key={method} className="flex items-center p-3 rounded-md cursor-pointer border"
|
||||
style={{
|
||||
borderColor: selectedPaymentMethod === method ? 'var(--primary)' : 'var(--input-border)',
|
||||
backgroundColor: selectedPaymentMethod === method ? 'var(--primary-light)' : 'var(--card-bg)'
|
||||
}}>
|
||||
<input
|
||||
type="radio"
|
||||
name="paymentMethod"
|
||||
value={method}
|
||||
checked={selectedPaymentMethod === method}
|
||||
onChange={() => handlePaymentMethodChange(method as PaymentMethod)}
|
||||
className="mr-3"
|
||||
/>
|
||||
<span style={{ color: 'var(--foreground)' }}>{label}</span>
|
||||
</label>
|
||||
))}
|
||||
<div className="p-3 rounded-md" style={{ background: 'var(--muted-light)', color: 'var(--foreground)' }}>
|
||||
<p className="text-sm font-semibold">
|
||||
{PAYMENT_METHOD_LABELS[paymentMethod]}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Payment Details - Only show when method is selected */}
|
||||
{selectedPaymentMethod && (
|
||||
<>
|
||||
{/* Payment Details */}
|
||||
<div>
|
||||
<p className="text-sm mb-2 font-medium" style={{ color: 'var(--muted)' }}>Recipient Address:</p>
|
||||
<div className="p-3 rounded-md overflow-hidden" style={{ background: 'var(--muted-light)', color: 'var(--foreground)' }}>
|
||||
<code className="text-sm font-mono break-all block">{getRecipientAddress(selectedPaymentMethod)}</code>
|
||||
<code className="text-sm font-mono break-all block">{getRecipientAddress(paymentMethod)}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -222,7 +209,7 @@ export default function PaymentModal({
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
{(selectedPaymentMethod === 'spl-token' && loadingPrice) ? (
|
||||
{(paymentMethod === 'spl-token' && loadingPrice) ? (
|
||||
<div className="w-full p-3 rounded-md flex items-center" style={{
|
||||
background: 'var(--muted-light)',
|
||||
border: '1px solid var(--input-border)',
|
||||
@ -249,23 +236,21 @@ export default function PaymentModal({
|
||||
readOnly
|
||||
/>
|
||||
)}
|
||||
{!(selectedPaymentMethod === 'spl-token' && loadingPrice) && (
|
||||
{!(paymentMethod === 'spl-token' && loadingPrice) && (
|
||||
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
||||
<span className="text-sm font-medium" style={{ color: 'var(--muted)' }}>
|
||||
{selectedPaymentMethod === 'sol' ? 'SOL' : tokenSymbol}
|
||||
{paymentMethod === 'sol' ? 'SOL' : tokenSymbol}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{selectedPaymentMethod === 'spl-token' && (
|
||||
{paymentMethod === 'spl-token' && (
|
||||
<p className="text-xs mt-1" style={{ color: 'var(--muted)' }}>
|
||||
Token information fetched from <a className='text-blue-400 underline' href={`https://jup.ag/tokens/${mintAddress}`} target="_blank" rel="noopener noreferrer">Jupiter</a>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="p-3 rounded-md text-sm" style={{ backgroundColor: 'var(--error-light)', color: 'var(--error)' }}>
|
||||
@ -291,11 +276,11 @@ export default function PaymentModal({
|
||||
onClick={handlePayment}
|
||||
className="px-5 py-2 rounded-md flex items-center transition-colors"
|
||||
style={{
|
||||
backgroundColor: (loading || loadingPrice || !selectedPaymentMethod) ? 'var(--muted)' : 'var(--primary)',
|
||||
backgroundColor: (loading || loadingPrice) ? 'var(--muted)' : 'var(--primary)',
|
||||
color: 'var(--primary-foreground)',
|
||||
opacity: (loading || loadingPrice || !selectedPaymentMethod) ? '0.8' : '1'
|
||||
opacity: (loading || loadingPrice) ? '0.8' : '1'
|
||||
}}
|
||||
disabled={loading || loadingPrice || !selectedPaymentMethod}
|
||||
disabled={loading || loadingPrice}
|
||||
>
|
||||
{(loading || loadingPrice) && (
|
||||
<svg className="animate-spin -ml-1 mr-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
@ -303,8 +288,7 @@ export default function PaymentModal({
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
)}
|
||||
{!selectedPaymentMethod ? 'Select Payment Method' :
|
||||
loadingPrice ? 'Loading Price...' :
|
||||
{loadingPrice ? 'Loading Price...' :
|
||||
loading ? 'Processing...' :
|
||||
'Pay with Solana Wallet'}
|
||||
</button>
|
||||
|
@ -10,6 +10,7 @@ import { SolflareWalletAdapter } from '@solana/wallet-adapter-solflare';
|
||||
// Default styles that can be overridden by your app
|
||||
import '@solana/wallet-adapter-react-ui/styles.css';
|
||||
import assert from 'assert';
|
||||
import { usePaymentMethod } from '@/contexts/PaymentMethodContext';
|
||||
|
||||
assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required');
|
||||
const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL;
|
||||
@ -19,12 +20,15 @@ interface WalletProvidersProps {
|
||||
}
|
||||
|
||||
export default function WalletProviders({ children }: WalletProvidersProps) {
|
||||
const { selectedPaymentMethod } = usePaymentMethod();
|
||||
|
||||
// Configure the Solana network endpoint
|
||||
const endpoint = useMemo(() => {
|
||||
return SOLANA_RPC_URL;
|
||||
}, []);
|
||||
|
||||
const wallets = useMemo(
|
||||
// All available wallet adapters
|
||||
const allWallets = useMemo(
|
||||
() => [
|
||||
new PhantomWalletAdapter(),
|
||||
new SolflareWalletAdapter(),
|
||||
@ -33,6 +37,24 @@ export default function WalletProviders({ children }: WalletProvidersProps) {
|
||||
[]
|
||||
);
|
||||
|
||||
// Filter wallets based on selected payment method
|
||||
const wallets = useMemo(() => {
|
||||
if (!selectedPaymentMethod) {
|
||||
// If no payment method selected, show all wallets
|
||||
return allWallets;
|
||||
}
|
||||
|
||||
return allWallets.filter(wallet => {
|
||||
const isBackpack = wallet.name.toLowerCase().includes('backpack');
|
||||
|
||||
if (selectedPaymentMethod === 'sol') {
|
||||
return isBackpack; // Only Backpack for SOL
|
||||
} else {
|
||||
return !isBackpack; // Only non-Backpack wallets for SPL tokens
|
||||
}
|
||||
});
|
||||
}, [allWallets, selectedPaymentMethod]);
|
||||
|
||||
return (
|
||||
<ConnectionProvider endpoint={endpoint}>
|
||||
<WalletProvider wallets={wallets} autoConnect>
|
||||
|
38
src/contexts/PaymentMethodContext.tsx
Normal file
38
src/contexts/PaymentMethodContext.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import React, { createContext, useContext, useState, ReactNode } from 'react';
|
||||
import { PaymentMethod } from '@/constants/payments';
|
||||
|
||||
interface PaymentMethodContextType {
|
||||
selectedPaymentMethod: PaymentMethod | null;
|
||||
setSelectedPaymentMethod: (method: PaymentMethod | null) => void;
|
||||
}
|
||||
|
||||
const PaymentMethodContext = createContext<PaymentMethodContextType | undefined>(undefined);
|
||||
|
||||
interface PaymentMethodProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function PaymentMethodProvider({ children }: PaymentMethodProviderProps) {
|
||||
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<PaymentMethod | null>(null);
|
||||
|
||||
return (
|
||||
<PaymentMethodContext.Provider
|
||||
value={{
|
||||
selectedPaymentMethod,
|
||||
setSelectedPaymentMethod
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</PaymentMethodContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function usePaymentMethod() {
|
||||
const context = useContext(PaymentMethodContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('usePaymentMethod must be used within a PaymentMethodProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
Loading…
Reference in New Issue
Block a user