307 lines
9.3 KiB
JavaScript
307 lines
9.3 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { loadStripe } from '@stripe/stripe-js';
|
|
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
|
|
import './UpgradeFlow.css';
|
|
|
|
const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY || 'pk_test_51QTaIiRu6l8tVuJxtest_placeholder');
|
|
|
|
/**
|
|
* Checkout form component
|
|
*/
|
|
function CheckoutForm({ tier, domain, onSuccess }) {
|
|
const stripe = useStripe();
|
|
const elements = useElements();
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState(null);
|
|
|
|
const handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
|
|
if (!stripe || !elements) return;
|
|
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
// Create payment method
|
|
const { error: pmError, paymentMethod } = await stripe.createPaymentMethod({
|
|
type: 'card',
|
|
card: elements.getElement(CardElement)
|
|
});
|
|
|
|
if (pmError) {
|
|
throw new Error(pmError.message);
|
|
}
|
|
|
|
// Subscribe or register domain
|
|
const endpoint = domain
|
|
? '/api/premium/domains/register'
|
|
: '/api/premium/subscribe';
|
|
|
|
const body = domain ? {
|
|
domain: domain,
|
|
walletAddress: window.ethereum?.selectedAddress || '0x0000000000000000000000000000000000000000',
|
|
paymentMethodId: paymentMethod.id
|
|
} : {
|
|
tier: tier,
|
|
paymentMethodId: paymentMethod.id,
|
|
billingPeriod: 'yearly'
|
|
};
|
|
|
|
const response = await fetch(endpoint, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
},
|
|
body: JSON.stringify(body)
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
onSuccess(data);
|
|
} else {
|
|
throw new Error(data.error || 'Subscription failed');
|
|
}
|
|
|
|
} catch (err) {
|
|
setError(err.message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const getAmount = () => {
|
|
if (domain) return '$100/year';
|
|
if (tier === 'premium') return '$100/year';
|
|
if (tier === 'enterprise') return '$500/month';
|
|
return '$0';
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="checkout-form">
|
|
<div className="card-element-wrapper">
|
|
<CardElement
|
|
options={{
|
|
style: {
|
|
base: {
|
|
fontSize: '16px',
|
|
color: '#fff',
|
|
'::placeholder': {
|
|
color: '#9ca3af'
|
|
}
|
|
},
|
|
invalid: {
|
|
color: '#ed4245'
|
|
}
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="error-message">{error}</div>
|
|
)}
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={!stripe || loading}
|
|
className="btn-submit"
|
|
>
|
|
{loading ? 'Processing...' : `Subscribe - ${getAmount()}`}
|
|
</button>
|
|
|
|
<p style={{ textAlign: 'center', marginTop: '16px', fontSize: '12px', color: '#aaa' }}>
|
|
By subscribing, you agree to our Terms of Service and Privacy Policy
|
|
</p>
|
|
</form>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Main upgrade flow component
|
|
*/
|
|
export default function UpgradeFlow({ currentTier = 'free' }) {
|
|
const [selectedTier, setSelectedTier] = useState('premium');
|
|
const [domainName, setDomainName] = useState('');
|
|
const [domainAvailable, setDomainAvailable] = useState(null);
|
|
const [checkingDomain, setCheckingDomain] = useState(false);
|
|
|
|
const checkDomain = async () => {
|
|
if (!domainName) {
|
|
setError('Please enter a domain name');
|
|
return;
|
|
}
|
|
|
|
setCheckingDomain(true);
|
|
setDomainAvailable(null);
|
|
|
|
try {
|
|
const response = await fetch('/api/premium/domains/check-availability', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
},
|
|
body: JSON.stringify({
|
|
domain: `${domainName}.aethex`
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
setDomainAvailable(data);
|
|
} else {
|
|
throw new Error(data.error);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Failed to check domain:', error);
|
|
setDomainAvailable({
|
|
available: false,
|
|
domain: `${domainName}.aethex`,
|
|
error: error.message
|
|
});
|
|
} finally {
|
|
setCheckingDomain(false);
|
|
}
|
|
};
|
|
|
|
const handleSuccess = (data) => {
|
|
alert('Subscription successful! Welcome to premium!');
|
|
// Redirect to dashboard or show success modal
|
|
window.location.href = '/dashboard';
|
|
};
|
|
|
|
return (
|
|
<div className="upgrade-flow">
|
|
<h1>Upgrade to Premium</h1>
|
|
|
|
<div className="tier-selection">
|
|
<div
|
|
className={`tier-card ${selectedTier === 'premium' ? 'selected' : ''}`}
|
|
onClick={() => setSelectedTier('premium')}
|
|
>
|
|
<h3>Premium</h3>
|
|
<div className="price">$100/year</div>
|
|
<ul>
|
|
<li>✓ Custom .aethex domain</li>
|
|
<li>✓ Blockchain NFT ownership</li>
|
|
<li>✓ Unlimited friends</li>
|
|
<li>✓ HD voice/video calls (1080p)</li>
|
|
<li>✓ 10 GB storage</li>
|
|
<li>✓ Custom branding</li>
|
|
<li>✓ Analytics dashboard</li>
|
|
<li>✓ Priority support</li>
|
|
<li>✓ Ad-free experience</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div
|
|
className={`tier-card ${selectedTier === 'enterprise' ? 'selected' : ''}`}
|
|
onClick={() => setSelectedTier('enterprise')}
|
|
>
|
|
<h3>Enterprise</h3>
|
|
<div className="price">$500+/month</div>
|
|
<ul>
|
|
<li>✓ Everything in Premium</li>
|
|
<li>✓ White-label platform</li>
|
|
<li>✓ Custom domain (chat.yoursite.com)</li>
|
|
<li>✓ Unlimited team members</li>
|
|
<li>✓ Dedicated infrastructure</li>
|
|
<li>✓ 4K video quality</li>
|
|
<li>✓ SLA guarantees (99.9% uptime)</li>
|
|
<li>✓ Dedicated account manager</li>
|
|
<li>✓ Custom integrations</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
{selectedTier === 'premium' && (
|
|
<div className="domain-selection">
|
|
<h3>Choose Your .aethex Domain</h3>
|
|
<p style={{ color: '#aaa', marginBottom: '16px' }}>
|
|
Your premium blockchain domain with NFT ownership proof
|
|
</p>
|
|
|
|
<div className="domain-input-group">
|
|
<input
|
|
type="text"
|
|
value={domainName}
|
|
onChange={(e) => setDomainName(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ''))}
|
|
placeholder="yourname"
|
|
className="domain-input"
|
|
maxLength={50}
|
|
/>
|
|
<span className="domain-suffix">.aethex</span>
|
|
<button
|
|
type="button"
|
|
onClick={checkDomain}
|
|
disabled={checkingDomain || !domainName}
|
|
>
|
|
{checkingDomain ? 'Checking...' : 'Check'}
|
|
</button>
|
|
</div>
|
|
|
|
{domainAvailable && (
|
|
<div className={`domain-status ${domainAvailable.available ? 'available' : 'unavailable'}`}>
|
|
{domainAvailable.available ? (
|
|
<>
|
|
<p><strong>✓ {domainAvailable.domain} is available!</strong></p>
|
|
<p style={{ fontSize: '14px', marginTop: '8px', opacity: 0.8 }}>
|
|
Price: ${domainAvailable.price}/year
|
|
</p>
|
|
</>
|
|
) : (
|
|
<div>
|
|
<p><strong>✗ {domainAvailable.domain} is taken</strong></p>
|
|
{domainAvailable.error && (
|
|
<p style={{ fontSize: '14px', marginTop: '4px' }}>{domainAvailable.error}</p>
|
|
)}
|
|
{domainAvailable.suggestedAlternatives && domainAvailable.suggestedAlternatives.length > 0 && (
|
|
<>
|
|
<p style={{ marginTop: '12px' }}>Try these alternatives:</p>
|
|
<ul>
|
|
{domainAvailable.suggestedAlternatives.map(alt => (
|
|
<li
|
|
key={alt}
|
|
onClick={() => setDomainName(alt.replace('.aethex', ''))}
|
|
>
|
|
{alt}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{(selectedTier === 'enterprise' || (selectedTier === 'premium' && domainAvailable?.available)) && (
|
|
<Elements stripe={stripePromise}>
|
|
<CheckoutForm
|
|
tier={selectedTier}
|
|
domain={domainAvailable?.available ? `${domainName}.aethex` : null}
|
|
onSuccess={handleSuccess}
|
|
/>
|
|
</Elements>
|
|
)}
|
|
|
|
{selectedTier === 'enterprise' && !domainAvailable && (
|
|
<div style={{ textAlign: 'center', padding: '40px', color: '#aaa' }}>
|
|
<p>For Enterprise plans, please contact our sales team:</p>
|
|
<p style={{ marginTop: '16px' }}>
|
|
<a href="mailto:enterprise@aethex.app" style={{ color: '#5865f2' }}>
|
|
enterprise@aethex.app
|
|
</a>
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|