Skip to content

Commit fc54e61

Browse files
committed
feat: implement premium page and database migration for premium features
1 parent b0feb7e commit fc54e61

11 files changed

Lines changed: 920 additions & 130 deletions

app/premium/page.tsx

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
'use client';
2+
3+
import { useState, useEffect } from 'react';
4+
import { useWallet, useConnection } from '@solana/wallet-adapter-react';
5+
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';
6+
import {
7+
PublicKey,
8+
Transaction,
9+
Connection
10+
} from '@solana/web3.js';
11+
import {
12+
TOKEN_PROGRAM_ID,
13+
ASSOCIATED_TOKEN_PROGRAM_ID,
14+
createTransferInstruction,
15+
getAssociatedTokenAddress,
16+
createAssociatedTokenAccountInstruction,
17+
getAccount
18+
} from '@solana/spl-token';
19+
import Header from '@/components/Header';
20+
import Footer from '@/components/Footer';
21+
import { supabase } from '../../lib/supabaseClient';
22+
import { Check, Shield, Zap, Wallet, ArrowRight } from 'lucide-react';
23+
24+
const USDC_MINT = new PublicKey('EPjFW36vc7J741696u75Rqd6j5uv69eeMT1p8rkAKz2w');
25+
const TREASURY_WALLET = new PublicKey('11111111111111111111111111111111'); // Placeholder
26+
const PREMIUM_PRICE_USDC = 29;
27+
28+
export default function PremiumPage() {
29+
const { publicKey, sendTransaction, connected } = useWallet();
30+
const { connection } = useConnection();
31+
32+
const [isProcessing, setIsProcessing] = useState(false);
33+
const [error, setError] = useState<string | null>(null);
34+
const [success, setSuccess] = useState(false);
35+
const [xmrWallet, setXmrWallet] = useState('');
36+
const [userPremiumStatus, setUserPremiumStatus] = useState(false);
37+
38+
useEffect(() => {
39+
if (connected && publicKey) {
40+
checkPremiumStatus();
41+
}
42+
}, [connected, publicKey, checkPremiumStatus]);
43+
44+
async function checkPremiumStatus() {
45+
const { data, error } = await supabase
46+
.from('users')
47+
.select('is_premium, xmr_wallet')
48+
.eq('solana_wallet', publicKey?.toBase58())
49+
.single();
50+
51+
if (data) {
52+
setUserPremiumStatus(data.is_premium);
53+
if (data.xmr_wallet) setXmrWallet(data.xmr_wallet);
54+
}
55+
}
56+
57+
async function handlePurchase() {
58+
if (!publicKey || !connected) {
59+
setError('Please connect your wallet first');
60+
return;
61+
}
62+
63+
if (!xmrWallet || xmrWallet.length < 90) { // Monero addresses are ~95 chars
64+
setError('Please enter a valid Monero wallet address');
65+
return;
66+
}
67+
68+
setIsProcessing(true);
69+
setError(null);
70+
71+
try {
72+
// 1. Get associated token accounts
73+
const fromAta = await getAssociatedTokenAddress(USDC_MINT, publicKey);
74+
const toAta = await getAssociatedTokenAddress(USDC_MINT, TREASURY_WALLET);
75+
76+
// 2. Create transaction
77+
const transaction = new Transaction();
78+
79+
// Check if destination ATA exists, if not add create instruction
80+
try {
81+
await getAccount(connection, toAta);
82+
} catch (e) {
83+
transaction.add(
84+
createAssociatedTokenAccountInstruction(
85+
publicKey,
86+
toAta,
87+
TREASURY_WALLET,
88+
USDC_MINT
89+
)
90+
);
91+
}
92+
93+
// Add transfer instruction
94+
// Note: We use 6 decimals for USDC
95+
transaction.add(
96+
createTransferInstruction(
97+
fromAta,
98+
toAta,
99+
publicKey,
100+
PREMIUM_PRICE_USDC * 1000000
101+
)
102+
);
103+
104+
// 3. Send and confirm
105+
const signature = await sendTransaction(transaction, connection);
106+
await connection.confirmTransaction(signature, 'confirmed');
107+
108+
// 4. Update Database
109+
const { error: dbError } = await supabase
110+
.from('users')
111+
.upsert({
112+
solana_wallet: publicKey.toBase58(),
113+
is_premium: true,
114+
xmr_wallet: xmrWallet
115+
});
116+
117+
if (dbError) throw dbError;
118+
119+
setSuccess(true);
120+
setUserPremiumStatus(true);
121+
} catch (err: any) {
122+
console.error('Purchase error:', err);
123+
setError(err.message || 'Transaction failed');
124+
} finally {
125+
setIsProcessing(false);
126+
}
127+
}
128+
129+
async function handleUpdateWallet() {
130+
if (!publicKey) return;
131+
132+
setIsProcessing(true);
133+
try {
134+
const { error } = await supabase
135+
.from('users')
136+
.update({ xmr_wallet: xmrWallet })
137+
.eq('solana_wallet', publicKey.toBase58());
138+
139+
if (error) throw error;
140+
alert('Monero wallet updated successfully!');
141+
} catch (err: any) {
142+
setError(err.message);
143+
} finally {
144+
setIsProcessing(false);
145+
}
146+
}
147+
148+
return (
149+
<div className="min-h-screen bg-black text-white flex flex-col">
150+
<Header />
151+
152+
<main className="flex-grow pt-32 pb-20 px-4 sm:px-6 lg:px-8 max-w-4xl mx-auto w-full">
153+
<div className="text-center mb-16">
154+
<div className="inline-block border border-yellow-400 px-4 py-2 mb-6">
155+
<span className="text-yellow-400 font-mono text-sm uppercase tracking-widest">
156+
Premium Feature
157+
</span>
158+
</div>
159+
<h1 className="text-5xl md:text-6xl font-bold tracking-tighter mb-6 uppercase">
160+
Direct <span className="text-yellow-400">Mining</span>
161+
</h1>
162+
<p className="text-xl text-zinc-400 max-w-2xl mx-auto">
163+
Bypass the platform pool and mine Monero directly to your own wallet.
164+
Keep 100% of your hashes with zero platform fees.
165+
</p>
166+
</div>
167+
168+
<div className="grid md:grid-cols-2 gap-8 items-start">
169+
{/* Feature Card */}
170+
<div className="bg-zinc-950 border border-zinc-800 p-8 space-y-6">
171+
<h2 className="text-2xl font-bold uppercase tracking-wide">Benefits</h2>
172+
<ul className="space-y-4">
173+
<li className="flex items-start gap-3">
174+
<Check className="w-5 h-5 text-yellow-400 mt-1" />
175+
<div>
176+
<p className="font-bold">Own Wallet Support</p>
177+
<p className="text-sm text-zinc-500">Specify any Monero address for payouts.</p>
178+
</div>
179+
</li>
180+
<li className="flex items-start gap-3">
181+
<Shield className="w-5 h-5 text-yellow-400 mt-1" />
182+
<div>
183+
<p className="font-bold">No Platform Fees</p>
184+
<p className="text-sm text-zinc-500">Mine directly to your chosen pool.</p>
185+
</div>
186+
</li>
187+
<li className="flex items-start gap-3">
188+
<Zap className="w-5 h-5 text-yellow-400 mt-1" />
189+
<div>
190+
<p className="font-bold">Full Control</p>
191+
<p className="text-sm text-zinc-500">Configure custom mining parameters.</p>
192+
</div>
193+
</li>
194+
</ul>
195+
196+
<div className="pt-6 border-t border-zinc-900">
197+
<div className="flex justify-between items-end mb-2">
198+
<span className="text-zinc-500 uppercase text-xs font-bold tracking-widest">One-time payment</span>
199+
<span className="text-4xl font-bold text-white">29 <span className="text-sm text-zinc-500">USDC</span></span>
200+
</div>
201+
</div>
202+
</div>
203+
204+
{/* Action Card */}
205+
<div className="bg-zinc-950 border-2 border-yellow-400/20 p-8 space-y-6">
206+
{!connected ? (
207+
<div className="text-center py-10 space-y-6">
208+
<Wallet className="w-16 h-16 text-zinc-700 mx-auto" />
209+
<p className="text-zinc-400 font-mono text-sm">Connect your Solana wallet to proceed</p>
210+
<div className="flex justify-center">
211+
<WalletMultiButton className="!bg-yellow-400 !text-black !font-bold" />
212+
</div>
213+
</div>
214+
) : userPremiumStatus ? (
215+
<div className="space-y-6">
216+
<div className="bg-yellow-400/10 border border-yellow-400/20 p-4 rounded flex items-center gap-3">
217+
<Check className="w-6 h-6 text-yellow-400" />
218+
<span className="text-yellow-400 font-bold uppercase text-sm tracking-wider">Premium Active</span>
219+
</div>
220+
221+
<div className="space-y-2">
222+
<label className="text-xs uppercase text-zinc-500 font-bold tracking-widest">Your Monero Wallet</label>
223+
<input
224+
type="text"
225+
value={xmrWallet}
226+
onChange={(e) => setXmrWallet(e.target.value)}
227+
placeholder="4..."
228+
className="w-full bg-black border border-zinc-800 p-3 font-mono text-sm focus:border-yellow-400 outline-none transition-colors"
229+
/>
230+
</div>
231+
232+
<button
233+
onClick={handleUpdateWallet}
234+
disabled={isProcessing}
235+
className="w-full bg-yellow-400 text-black font-bold py-4 uppercase tracking-widest hover:bg-yellow-500 transition-colors disabled:opacity-50"
236+
>
237+
{isProcessing ? 'Updating...' : 'Update Settings'}
238+
</button>
239+
</div>
240+
) : (
241+
<div className="space-y-6">
242+
<div className="space-y-2">
243+
<label className="text-xs uppercase text-zinc-500 font-bold tracking-widest">Monero Wallet for Mining</label>
244+
<input
245+
type="text"
246+
value={xmrWallet}
247+
onChange={(e) => setXmrWallet(e.target.value)}
248+
placeholder="Enter your XMR address"
249+
className="w-full bg-black border border-zinc-800 p-3 font-mono text-sm focus:border-yellow-400 outline-none transition-colors"
250+
/>
251+
<p className="text-[10px] text-zinc-600 uppercase tracking-tighter">Your hardware will mine to this address</p>
252+
</div>
253+
254+
{error && (
255+
<div className="bg-red-500/10 border border-red-500/20 p-3 text-red-400 text-xs font-mono">
256+
{error}
257+
</div>
258+
)}
259+
260+
<button
261+
onClick={handlePurchase}
262+
disabled={isProcessing}
263+
className="w-full bg-yellow-400 text-black font-bold py-4 uppercase tracking-widest hover:bg-yellow-500 transition-colors flex items-center justify-center gap-2 group disabled:opacity-50"
264+
>
265+
{isProcessing ? (
266+
<span className="animate-pulse">Processing...</span>
267+
) : (
268+
<>
269+
Unlock Direct Mining
270+
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
271+
</>
272+
)}
273+
</button>
274+
275+
<p className="text-[10px] text-zinc-600 text-center font-mono uppercase">
276+
Payment processed via Solana • Instant Activation
277+
</p>
278+
</div>
279+
)}
280+
</div>
281+
</div>
282+
283+
{/* Success Modal Overlay */}
284+
{success && (
285+
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/90 p-4">
286+
<div className="bg-zinc-950 border-4 border-yellow-400 p-12 max-w-lg text-center space-y-6">
287+
<div className="w-20 h-20 bg-yellow-400 rounded-full flex items-center justify-center mx-auto">
288+
<Check className="w-12 h-12 text-black" />
289+
</div>
290+
<h2 className="text-4xl font-bold uppercase tracking-tighter">Premium Unlocked</h2>
291+
<p className="text-zinc-400">
292+
You can now use your custom Monero wallet in the MineBench Desktop app.
293+
Restart your miner to apply changes.
294+
</p>
295+
<button
296+
onClick={() => setSuccess(false)}
297+
className="btn-primary w-full"
298+
>
299+
Let&apos;s Mine
300+
</button>
301+
</div>
302+
</div>
303+
)}
304+
</main>
305+
306+
<Footer />
307+
</div>
308+
);
309+
}

apply-migration.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const { createClient } = require('@supabase/supabase-js');
2+
const fs = require('fs');
3+
const path = require('path');
4+
require('dotenv').config({ path: '.env.local' });
5+
6+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
7+
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
8+
9+
if (!supabaseUrl || !supabaseServiceKey) {
10+
console.error('Missing Supabase environment variables');
11+
process.exit(1);
12+
}
13+
14+
const supabase = createClient(supabaseUrl, supabaseServiceKey);
15+
16+
async function applyMigration() {
17+
const migrationPath = path.join(__dirname, 'supabase', 'migrations', '003_add_premium_features.sql');
18+
const sql = fs.readFileSync(migrationPath, 'utf8');
19+
20+
console.log('Applying migration...');
21+
const { error } = await supabase.rpc('exec_sql', { sql_query: sql });
22+
23+
if (error) {
24+
// If rpc exec_sql is not available, we might need another way or just report it
25+
console.error('Error applying migration via RPC:', error);
26+
console.log('Note: If "exec_sql" is missing, you may need to apply the SQL via Supabase Dashboard UI.');
27+
} else {
28+
console.log('Migration applied successfully!');
29+
}
30+
}
31+
32+
applyMigration();

build_error.txt

1.23 KB
Binary file not shown.

build_error_utf8.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
 тЦ▓ Next.js 14.2.33
2+
- Environments: .env.local
3+
4+
Creating an optimized production build ...
5+
node.exe : Failed to co
6+
mpile.
7+
At C:\Program Files\nod
8+
ejs\npx.ps1:29 char:3
9+
+ & $NODE_EXE $NPX_CL
10+
I_JS $args
11+
+ ~~~~~~~~~~~~~~~~~~~
12+
~~~~~~~~~~
13+
+ CategoryInfo
14+
: NotSpecifi
15+
ed: (Failed to com
16+
pile.:String) [],
17+
RemoteException
18+
+ FullyQualifiedEr
19+
rorId : NativeComm
20+
andError
21+
22+
23+
./app/premium/page.tsx
24+
Module not found: Can't
25+
resolve '@solana/spl-t
26+
oken'
27+
28+
https://nextjs.org/docs
29+
/messages/module-not-fo
30+
und
31+
32+
33+
> Build failed because
34+
of webpack errors

components/Header.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default function Header({ onRefresh, loading }: HeaderProps = {}) {
1818
{ name: 'Home', href: '/' },
1919
{ name: 'Dashboard', href: '/dashboard' },
2020
{ name: 'Downloads', href: '/downloads' },
21+
{ name: 'Premium', href: '/premium' },
2122
{ name: 'Whitepaper', href: '/whitepaper' },
2223
];
2324

last_build_error.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+

2+
> andError
3+
4+
5+
> Module not found: Can
6+
't
7+
resolve '@solana/spl
8+
-t
9+
oken'
10+
> of webpack errors
11+
12+

0 commit comments

Comments
 (0)