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_)
|
# Client-side environment variables (must be prefixed with NEXT_PUBLIC_)
|
||||||
|
|
||||||
# Solana Token Payment Configuration
|
# Solana Payment Configuration
|
||||||
# TODO: Use different RPC URL or use browser wallet
|
|
||||||
NEXT_PUBLIC_SOLANA_RPC_URL=https://skilled-prettiest-seed.solana-mainnet.quiknode.pro/eeecfebd04e345f69f1900cc3483cbbfea02a158
|
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
|
NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS=71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFELvg
|
||||||
|
|
||||||
# Multisig address
|
# Multisig address
|
||||||
|
@ -15,7 +15,10 @@ import { PaymentMethod, SOL_PAYMENT_AMOUNT_LAMPORTS } from '@/constants/payments
|
|||||||
import { getRecipientAddress } from '@/services/solana';
|
import { getRecipientAddress } from '@/services/solana';
|
||||||
|
|
||||||
assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required');
|
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_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
|
// Allow 20% slippage due to price fluctuations
|
||||||
const ALLOWED_SLIPPAGE_FACTOR = 0.2
|
const ALLOWED_SLIPPAGE_FACTOR = 0.2
|
||||||
@ -136,16 +139,19 @@ export const registryTransactionWithRetry = async (
|
|||||||
throw lastError;
|
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) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
if (!connection) {
|
|
||||||
connection = new Connection(SOLANA_RPC_URL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// First check if the request body is valid JSON
|
// First check if the request body is valid JSON
|
||||||
let url, txHash, senderPublicKey, paymentMethod;
|
let url, txHash, senderPublicKey, paymentMethod;
|
||||||
|
let connection: Connection;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
@ -153,6 +159,9 @@ export async function POST(request: NextRequest) {
|
|||||||
txHash = body.txHash;
|
txHash = body.txHash;
|
||||||
paymentMethod = body.paymentMethod as PaymentMethod;
|
paymentMethod = body.paymentMethod as PaymentMethod;
|
||||||
|
|
||||||
|
// Get the appropriate connection based on payment method
|
||||||
|
connection = getConnection(paymentMethod);
|
||||||
|
|
||||||
const tx = await connection.getParsedTransaction(txHash, 'confirmed');
|
const tx = await connection.getParsedTransaction(txHash, 'confirmed');
|
||||||
if (!tx) {
|
if (!tx) {
|
||||||
console.error("Transaction not found.");
|
console.error("Transaction not found.");
|
||||||
|
@ -4,6 +4,7 @@ import { Geist, Geist_Mono } from "next/font/google";
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import ErrorBoundaryWrapper from "../components/ErrorBoundaryWrapper";
|
import ErrorBoundaryWrapper from "../components/ErrorBoundaryWrapper";
|
||||||
import WalletProviders from "../components/WalletProviders";
|
import WalletProviders from "../components/WalletProviders";
|
||||||
|
import { PaymentMethodProvider } from "../contexts/PaymentMethodContext";
|
||||||
|
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
@ -32,9 +33,11 @@ export default function RootLayout({
|
|||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
<ErrorBoundaryWrapper />
|
<ErrorBoundaryWrapper />
|
||||||
<WalletProviders>
|
<PaymentMethodProvider>
|
||||||
{children}
|
<WalletProviders>
|
||||||
</WalletProviders>
|
{children}
|
||||||
|
</WalletProviders>
|
||||||
|
</PaymentMethodProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
180
src/app/page.tsx
180
src/app/page.tsx
@ -7,14 +7,17 @@ import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';
|
|||||||
import URLForm from '@/components/URLForm';
|
import URLForm from '@/components/URLForm';
|
||||||
import StatusDisplay from '@/components/StatusDisplay';
|
import StatusDisplay from '@/components/StatusDisplay';
|
||||||
import { createApplicationDeploymentRequest } from '@/services/registry';
|
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 { useWallet } from '@solana/wallet-adapter-react';
|
||||||
|
|
||||||
|
import { usePaymentMethod } from '@/contexts/PaymentMethodContext';
|
||||||
|
|
||||||
// Dynamically import components to avoid SSR issues with browser APIs
|
// Dynamically import components to avoid SSR issues with browser APIs
|
||||||
const PaymentModal = dynamic(() => import('@/components/PaymentModal'), { ssr: false });
|
const PaymentModal = dynamic(() => import('@/components/PaymentModal'), { ssr: false });
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const { wallet, connected, publicKey } = useWallet();
|
const { wallet, connected, publicKey } = useWallet();
|
||||||
|
const { selectedPaymentMethod, setSelectedPaymentMethod } = usePaymentMethod();
|
||||||
const [url, setUrl] = useState<string | null>(null);
|
const [url, setUrl] = useState<string | null>(null);
|
||||||
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
||||||
const [status, setStatus] = useState<'idle' | 'creating' | 'success' | 'error'>('idle');
|
const [status, setStatus] = useState<'idle' | 'creating' | 'success' | 'error'>('idle');
|
||||||
@ -32,6 +35,20 @@ export default function Home() {
|
|||||||
setShowPaymentModal(true);
|
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) => {
|
const handlePaymentComplete = useCallback(async (hash: string, paymentMethod: PaymentMethod) => {
|
||||||
if (!publicKey || !url) {
|
if (!publicKey || !url) {
|
||||||
return
|
return
|
||||||
@ -85,62 +102,143 @@ export default function Home() {
|
|||||||
Deploy Frontends with {process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} + Laconic
|
Deploy Frontends with {process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} + Laconic
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
{/* Step 1: Payment Method Selection */}
|
||||||
<div className="mb-10 p-6 rounded-lg" style={{ background: 'var(--muted-light)', borderLeft: '4px solid var(--primary)' }}>
|
<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">
|
<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"
|
<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>
|
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>1</span>
|
||||||
Connect Your Wallet
|
Choose Payment Method
|
||||||
</h2>
|
</h2>
|
||||||
<div className="text-center">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<p className="mb-4" style={{ color: 'var(--muted-foreground)' }}>
|
<button
|
||||||
Payment methods: <span className="font-semibold" style={{ color: 'var(--foreground)' }}>
|
onClick={() => setSelectedPaymentMethod('sol')}
|
||||||
SOL or {process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} (Solana)
|
className={`p-4 rounded-lg border-2 transition-colors ${
|
||||||
</span>
|
selectedPaymentMethod === 'sol' ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-gray-400'
|
||||||
</p>
|
}`}
|
||||||
{connected && publicKey ? (
|
style={{
|
||||||
<div className="flex flex-col items-center space-y-3">
|
backgroundColor: selectedPaymentMethod === 'sol' ? 'var(--accent)' : 'var(--card-bg)',
|
||||||
<div className="flex items-center">
|
borderColor: selectedPaymentMethod === 'sol' ? 'var(--primary)' : 'var(--card-border)'
|
||||||
<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})
|
<div className="text-left">
|
||||||
</p>
|
<h3 className="font-semibold text-lg mb-2">Native SOL</h3>
|
||||||
</div>
|
<p className="text-sm" style={{ color: 'var(--muted-foreground)' }}>
|
||||||
<div className="w-full p-3 rounded-md" style={{ background: 'var(--card-bg)', border: '1px solid var(--card-border)' }}>
|
Pay 0.01 SOL (~$2-5 USD)
|
||||||
<p className="text-sm font-mono break-all text-center">{publicKey.toBase58()}</p>
|
</p>
|
||||||
</div>
|
<p className="text-xs mt-1" style={{ color: 'var(--muted-foreground)' }}>
|
||||||
<WalletMultiButton
|
Compatible with: Backpack
|
||||||
className="!px-4 !py-2 !rounded-md !text-sm !transition-colors"
|
</p>
|
||||||
style={{
|
|
||||||
backgroundColor: 'var(--muted)',
|
|
||||||
color: 'var(--foreground)',
|
|
||||||
border: '1px solid var(--input-border)'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</button>
|
||||||
<WalletMultiButton
|
<button
|
||||||
className="!px-6 !py-3 !rounded-md !w-full !transition-colors"
|
onClick={() => setSelectedPaymentMethod('spl-token')}
|
||||||
style={{
|
className={`p-4 rounded-lg border-2 transition-colors ${
|
||||||
backgroundColor: 'var(--primary)',
|
selectedPaymentMethod === 'spl-token' ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-gray-400'
|
||||||
color: 'var(--primary-foreground)',
|
}`}
|
||||||
}}
|
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>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-8 p-6 rounded-lg" style={{
|
|
||||||
|
{/* Step 2: Wallet Connection */}
|
||||||
|
<div className="mb-10 p-6 rounded-lg" style={{
|
||||||
background: 'var(--muted-light)',
|
background: 'var(--muted-light)',
|
||||||
borderLeft: '4px solid var(--primary)',
|
borderLeft: '4px solid var(--primary)',
|
||||||
opacity: connected ? '1' : '0.6'
|
opacity: selectedPaymentMethod ? '1' : '0.6'
|
||||||
}}>
|
}}>
|
||||||
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
<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"
|
<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)' }}>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)' }}>
|
||||||
|
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: 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>
|
||||||
|
<WalletMultiButton
|
||||||
|
className="!px-4 !py-2 !rounded-md !text-sm !transition-colors"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--muted)',
|
||||||
|
color: 'var(--foreground)',
|
||||||
|
border: '1px solid var(--input-border)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</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={{
|
||||||
|
backgroundColor: 'var(--primary)',
|
||||||
|
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 && 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)' }}>3</span>
|
||||||
Enter URL to Deploy
|
Enter URL to Deploy
|
||||||
</h2>
|
</h2>
|
||||||
<URLForm
|
<URLForm
|
||||||
onSubmit={handleUrlSubmit}
|
onSubmit={handleUrlSubmit}
|
||||||
disabled={!connected || status === 'creating'}
|
disabled={!connected || !isWalletCompatible() || status === 'creating'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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)' }}>
|
<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">
|
<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"
|
<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
|
Deployment Status
|
||||||
</h2>
|
</h2>
|
||||||
<StatusDisplay
|
<StatusDisplay
|
||||||
@ -166,7 +264,7 @@ export default function Home() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showPaymentModal && url && connected && publicKey && (
|
{showPaymentModal && url && connected && publicKey && selectedPaymentMethod && (
|
||||||
<PaymentModal
|
<PaymentModal
|
||||||
isOpen={showPaymentModal}
|
isOpen={showPaymentModal}
|
||||||
onClose={handleClosePaymentModal}
|
onClose={handleClosePaymentModal}
|
||||||
|
@ -9,10 +9,12 @@ import { useWallet } from '@solana/wallet-adapter-react';
|
|||||||
import { sendSolanaPayment, getRecipientAddress } from '@/services/solana';
|
import { sendSolanaPayment, getRecipientAddress } from '@/services/solana';
|
||||||
import { getRequiredTokenInfo } from '@/services/jupiter-price';
|
import { getRequiredTokenInfo } from '@/services/jupiter-price';
|
||||||
import { PaymentModalProps, PaymentRequest } from '@/types';
|
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');
|
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_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({
|
export default function PaymentModal({
|
||||||
isOpen,
|
isOpen,
|
||||||
@ -22,7 +24,8 @@ export default function PaymentModal({
|
|||||||
}: PaymentModalProps) {
|
}: PaymentModalProps) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<PaymentMethod | null>(null);
|
const { selectedPaymentMethod } = usePaymentMethod();
|
||||||
|
const paymentMethod = selectedPaymentMethod;
|
||||||
const [tokenAmount, setTokenAmount] = useState<number>(0);
|
const [tokenAmount, setTokenAmount] = useState<number>(0);
|
||||||
const [tokenDecimals, setTokenDecimals] = useState<number>(6); // Default fallback
|
const [tokenDecimals, setTokenDecimals] = useState<number>(6); // Default fallback
|
||||||
const [loadingPrice, setLoadingPrice] = useState(false);
|
const [loadingPrice, setLoadingPrice] = useState(false);
|
||||||
@ -30,6 +33,10 @@ export default function PaymentModal({
|
|||||||
const { wallet, publicKey } = useWallet();
|
const { wallet, publicKey } = useWallet();
|
||||||
|
|
||||||
const directConnection = useMemo(() => new Connection(SOLANA_RPC_URL), []);
|
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
|
// Get configuration from environment variables
|
||||||
const targetUsdAmount = parseFloat(process.env.NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD!);
|
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
|
// Fetch token amount based on USD price when SPL token method is selected
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen || selectedPaymentMethod !== 'spl-token') {
|
if (!isOpen || paymentMethod !== 'spl-token') {
|
||||||
setLoadingPrice(false);
|
setLoadingPrice(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -60,35 +67,30 @@ export default function PaymentModal({
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchTokenAmount();
|
fetchTokenAmount();
|
||||||
}, [isOpen, selectedPaymentMethod, targetUsdAmount, mintAddress]);
|
}, [isOpen, paymentMethod, targetUsdAmount, mintAddress]);
|
||||||
|
|
||||||
// Reset state when modal opens
|
// Initialize payment method when modal opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
setSelectedPaymentMethod(null);
|
|
||||||
setError('');
|
setError('');
|
||||||
setTokenAmount(0);
|
|
||||||
setLoadingPrice(false);
|
setLoadingPrice(false);
|
||||||
}
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
const handlePaymentMethodChange = (method: PaymentMethod) => {
|
// Set tokenAmount for SOL payments to maintain consistency
|
||||||
setSelectedPaymentMethod(method);
|
if (paymentMethod === 'sol') {
|
||||||
setError('');
|
setTokenAmount(SOL_PAYMENT_AMOUNT_LAMPORTS);
|
||||||
|
} else {
|
||||||
// Set tokenAmount for SOL payments to maintain consistency
|
setTokenAmount(0);
|
||||||
if (method === 'sol') {
|
}
|
||||||
setTokenAmount(SOL_PAYMENT_AMOUNT_LAMPORTS);
|
|
||||||
}
|
}
|
||||||
};
|
}, [isOpen, paymentMethod]);
|
||||||
|
|
||||||
|
|
||||||
const handlePayment = useCallback(async () => {
|
const handlePayment = useCallback(async () => {
|
||||||
if (!selectedPaymentMethod) {
|
if (!paymentMethod) {
|
||||||
setError('Please select a payment method.');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokenAmount === 0 || (selectedPaymentMethod === 'spl-token' && loadingPrice)) {
|
if (tokenAmount === 0 || (paymentMethod === 'spl-token' && loadingPrice)) {
|
||||||
setError('Payment amount not ready. Please wait.');
|
setError('Payment amount not ready. Please wait.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -103,20 +105,23 @@ export default function PaymentModal({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const paymentRequest: PaymentRequest = {
|
const paymentRequest: PaymentRequest = {
|
||||||
paymentMethod: selectedPaymentMethod,
|
paymentMethod: paymentMethod,
|
||||||
amount: tokenAmount,
|
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(
|
const result = await sendSolanaPayment(
|
||||||
wallet.adapter,
|
wallet.adapter,
|
||||||
directConnection,
|
connectionToUse,
|
||||||
publicKey!.toBase58(),
|
publicKey!.toBase58(),
|
||||||
paymentRequest
|
paymentRequest
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success && result.transactionSignature) {
|
if (result.success && result.transactionSignature) {
|
||||||
onPaymentComplete(result.transactionSignature, selectedPaymentMethod);
|
onPaymentComplete(result.transactionSignature, paymentMethod);
|
||||||
} else {
|
} else {
|
||||||
setError(result.error || 'Payment failed. Please try again.');
|
setError(result.error || 'Payment failed. Please try again.');
|
||||||
}
|
}
|
||||||
@ -125,12 +130,10 @@ export default function PaymentModal({
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [selectedPaymentMethod, tokenAmount, loadingPrice, wallet, directConnection, publicKey, onPaymentComplete]);
|
}, [paymentMethod, tokenAmount, loadingPrice, wallet, directConnection, solConnection, publicKey, onPaymentComplete]);
|
||||||
|
|
||||||
const getPaymentAmountDisplay = () => {
|
const getPaymentAmountDisplay = () => {
|
||||||
if (!selectedPaymentMethod) return '';
|
switch (paymentMethod) {
|
||||||
|
|
||||||
switch (selectedPaymentMethod) {
|
|
||||||
case 'sol':
|
case 'sol':
|
||||||
return `${SOL_PAYMENT_AMOUNT_LAMPORTS / LAMPORTS_PER_SOL} SOL`;
|
return `${SOL_PAYMENT_AMOUNT_LAMPORTS / LAMPORTS_PER_SOL} SOL`;
|
||||||
case 'spl-token':
|
case 'spl-token':
|
||||||
@ -141,7 +144,7 @@ export default function PaymentModal({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen || !paymentMethod) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 flex items-center justify-center p-4 z-50" style={{ background: 'rgba(15, 23, 42, 0.75)' }}>
|
<div className="fixed inset-0 flex items-center justify-center p-4 z-50" style={{ background: 'rgba(15, 23, 42, 0.75)' }}>
|
||||||
@ -161,111 +164,93 @@ export default function PaymentModal({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Payment Method Selection */}
|
{/* Payment Method Display */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium mb-3" style={{ color: 'var(--foreground)' }}>
|
<label className="block text-sm font-medium mb-3" style={{ color: 'var(--foreground)' }}>
|
||||||
Select Payment Method *
|
Payment Method
|
||||||
</label>
|
</label>
|
||||||
<div className="space-y-2">
|
<div className="p-3 rounded-md" style={{ background: 'var(--muted-light)', color: 'var(--foreground)' }}>
|
||||||
{Object.entries(PAYMENT_METHOD_LABELS).map(([method, label]) => (
|
<p className="text-sm font-semibold">
|
||||||
<label key={method} className="flex items-center p-3 rounded-md cursor-pointer border"
|
{PAYMENT_METHOD_LABELS[paymentMethod]}
|
||||||
style={{
|
</p>
|
||||||
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Payment Details - Only show when method is selected */}
|
{/* Payment Details */}
|
||||||
{selectedPaymentMethod && (
|
<div>
|
||||||
<>
|
<p className="text-sm mb-2 font-medium" style={{ color: 'var(--muted)' }}>Recipient Address:</p>
|
||||||
<div>
|
<div className="p-3 rounded-md overflow-hidden" style={{ background: 'var(--muted-light)', color: 'var(--foreground)' }}>
|
||||||
<p className="text-sm mb-2 font-medium" style={{ color: 'var(--muted)' }}>Recipient Address:</p>
|
<code className="text-sm font-mono break-all block">{getRecipientAddress(paymentMethod)}</code>
|
||||||
<div className="p-3 rounded-md overflow-hidden" style={{ background: 'var(--muted-light)', color: 'var(--foreground)' }}>
|
</div>
|
||||||
<code className="text-sm font-mono break-all block">{getRecipientAddress(selectedPaymentMethod)}</code>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--foreground)' }}>
|
||||||
|
Payment Amount
|
||||||
|
</label>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={targetUsdAmount}
|
||||||
|
disabled={true}
|
||||||
|
className="w-full p-3 pr-12 rounded-md"
|
||||||
|
style={{
|
||||||
|
background: 'var(--card-bg)',
|
||||||
|
border: '1px solid var(--input-border)',
|
||||||
|
color: 'var(--foreground)',
|
||||||
|
opacity: '0.7'
|
||||||
|
}}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<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)' }}>USD</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="relative">
|
||||||
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--foreground)' }}>
|
{(paymentMethod === 'spl-token' && loadingPrice) ? (
|
||||||
Payment Amount
|
<div className="w-full p-3 rounded-md flex items-center" style={{
|
||||||
</label>
|
background: 'var(--muted-light)',
|
||||||
<div className="space-y-3">
|
border: '1px solid var(--input-border)',
|
||||||
<div className="relative">
|
color: 'var(--muted)'
|
||||||
<input
|
}}>
|
||||||
type="text"
|
<svg className="animate-spin h-4 w-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
value={targetUsdAmount}
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
disabled={true}
|
<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>
|
||||||
className="w-full p-3 pr-12 rounded-md"
|
</svg>
|
||||||
style={{
|
Fetching token amount...
|
||||||
background: 'var(--card-bg)',
|
|
||||||
border: '1px solid var(--input-border)',
|
|
||||||
color: 'var(--foreground)',
|
|
||||||
opacity: '0.7'
|
|
||||||
}}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
<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)' }}>USD</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
<div className="relative">
|
<input
|
||||||
{(selectedPaymentMethod === 'spl-token' && loadingPrice) ? (
|
type="text"
|
||||||
<div className="w-full p-3 rounded-md flex items-center" style={{
|
value={getPaymentAmountDisplay()}
|
||||||
background: 'var(--muted-light)',
|
disabled={true}
|
||||||
border: '1px solid var(--input-border)',
|
className="w-full p-3 pr-20 rounded-md"
|
||||||
color: 'var(--muted)'
|
style={{
|
||||||
}}>
|
background: 'var(--card-bg)',
|
||||||
<svg className="animate-spin h-4 w-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
border: '1px solid var(--input-border)',
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
color: 'var(--foreground)',
|
||||||
<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>
|
opacity: '0.7'
|
||||||
</svg>
|
}}
|
||||||
Fetching token amount...
|
readOnly
|
||||||
</div>
|
/>
|
||||||
) : (
|
)}
|
||||||
<input
|
{!(paymentMethod === 'spl-token' && loadingPrice) && (
|
||||||
type="text"
|
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
||||||
value={getPaymentAmountDisplay()}
|
<span className="text-sm font-medium" style={{ color: 'var(--muted)' }}>
|
||||||
disabled={true}
|
{paymentMethod === 'sol' ? 'SOL' : tokenSymbol}
|
||||||
className="w-full p-3 pr-20 rounded-md"
|
</span>
|
||||||
style={{
|
|
||||||
background: 'var(--card-bg)',
|
|
||||||
border: '1px solid var(--input-border)',
|
|
||||||
color: 'var(--foreground)',
|
|
||||||
opacity: '0.7'
|
|
||||||
}}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!(selectedPaymentMethod === '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}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{selectedPaymentMethod === '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>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)}
|
{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 && (
|
{error && (
|
||||||
<div className="p-3 rounded-md text-sm" style={{ backgroundColor: 'var(--error-light)', color: 'var(--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}
|
onClick={handlePayment}
|
||||||
className="px-5 py-2 rounded-md flex items-center transition-colors"
|
className="px-5 py-2 rounded-md flex items-center transition-colors"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: (loading || loadingPrice || !selectedPaymentMethod) ? 'var(--muted)' : 'var(--primary)',
|
backgroundColor: (loading || loadingPrice) ? 'var(--muted)' : 'var(--primary)',
|
||||||
color: 'var(--primary-foreground)',
|
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) && (
|
{(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">
|
<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>
|
<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>
|
</svg>
|
||||||
)}
|
)}
|
||||||
{!selectedPaymentMethod ? 'Select Payment Method' :
|
{loadingPrice ? 'Loading Price...' :
|
||||||
loadingPrice ? 'Loading Price...' :
|
|
||||||
loading ? 'Processing...' :
|
loading ? 'Processing...' :
|
||||||
'Pay with Solana Wallet'}
|
'Pay with Solana Wallet'}
|
||||||
</button>
|
</button>
|
||||||
|
@ -10,6 +10,7 @@ import { SolflareWalletAdapter } from '@solana/wallet-adapter-solflare';
|
|||||||
// Default styles that can be overridden by your app
|
// Default styles that can be overridden by your app
|
||||||
import '@solana/wallet-adapter-react-ui/styles.css';
|
import '@solana/wallet-adapter-react-ui/styles.css';
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
import { usePaymentMethod } from '@/contexts/PaymentMethodContext';
|
||||||
|
|
||||||
assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required');
|
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_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL;
|
||||||
@ -19,12 +20,15 @@ interface WalletProvidersProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function WalletProviders({ children }: WalletProvidersProps) {
|
export default function WalletProviders({ children }: WalletProvidersProps) {
|
||||||
|
const { selectedPaymentMethod } = usePaymentMethod();
|
||||||
|
|
||||||
// Configure the Solana network endpoint
|
// Configure the Solana network endpoint
|
||||||
const endpoint = useMemo(() => {
|
const endpoint = useMemo(() => {
|
||||||
return SOLANA_RPC_URL;
|
return SOLANA_RPC_URL;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const wallets = useMemo(
|
// All available wallet adapters
|
||||||
|
const allWallets = useMemo(
|
||||||
() => [
|
() => [
|
||||||
new PhantomWalletAdapter(),
|
new PhantomWalletAdapter(),
|
||||||
new SolflareWalletAdapter(),
|
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 (
|
return (
|
||||||
<ConnectionProvider endpoint={endpoint}>
|
<ConnectionProvider endpoint={endpoint}>
|
||||||
<WalletProvider wallets={wallets} autoConnect>
|
<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