initial commit
This commit is contained in:
431
app/services/vpn/page.tsx
Normal file
431
app/services/vpn/page.tsx
Normal file
@@ -0,0 +1,431 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Header } from '@/components/header'
|
||||
import { Footer } from '@/components/footer'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Shield, MapPin, Download, QrCode, AlertCircle, Lock, Globe, Zap, Eye } from 'lucide-react'
|
||||
import { useAuth } from '@/contexts/AuthContext'
|
||||
import { env } from 'process'
|
||||
|
||||
interface VPNLocation {
|
||||
code: string
|
||||
name: string
|
||||
flag: string
|
||||
popular?: boolean
|
||||
endpoint: string
|
||||
}
|
||||
|
||||
export default function VPNPage() {
|
||||
const router = useRouter()
|
||||
const { user } = useAuth()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [billingCycle, setBillingCycle] = useState<'monthly' | 'yearly'>('monthly')
|
||||
const [selectedLocation, setSelectedLocation] = useState('')
|
||||
const [showConfig, setShowConfig] = useState(false)
|
||||
const [apiEndpoint, setApiEndpoint] = useState('')
|
||||
const [mockConfig, setMockConfig] = useState()
|
||||
// VPN pricing
|
||||
const pricing = {
|
||||
monthly: 300,
|
||||
yearly: 4023, // Save 17%
|
||||
}
|
||||
|
||||
// Available VPN locations
|
||||
const locations: VPNLocation[] = [
|
||||
// { code: 'india', name: 'Mumbai, India', flag: '🇮🇳', popular: true },
|
||||
{
|
||||
code: 'america',
|
||||
name: 'America, USA',
|
||||
flag: '🇺🇸',
|
||||
popular: true,
|
||||
endpoint: 'https://wireguard-vpn.3027622.siliconpin.com/vpn',
|
||||
},
|
||||
{
|
||||
code: 'europe',
|
||||
name: 'Europe, UK',
|
||||
flag: '🇬🇧',
|
||||
popular: true,
|
||||
endpoint: 'https://wireguard.vps20.siliconpin.com/vpn',
|
||||
},
|
||||
// { code: 'singapore', name: 'Singapore', flag: '🇸🇬' },
|
||||
// { code: 'japan', name: 'Tokyo, Japan', flag: '🇯🇵' },
|
||||
// { code: 'canada', name: 'Toronto, Canada', flag: '🇨🇦' },
|
||||
]
|
||||
|
||||
// Mock user balance
|
||||
const userBalance = 8000
|
||||
|
||||
const handleSetupVPN = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
|
||||
if (!user) {
|
||||
router.push('/auth?redirect=/services/vpn')
|
||||
return
|
||||
}
|
||||
|
||||
if (!selectedLocation) {
|
||||
setError('Please select a VPN location')
|
||||
return
|
||||
}
|
||||
|
||||
const totalPrice = pricing[billingCycle]
|
||||
if (userBalance < totalPrice) {
|
||||
setError(`Insufficient balance. You need ₹${totalPrice} but only have ₹${userBalance}`)
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
// Generate a unique order ID (you might want to use a proper ID generation)
|
||||
const orderId = `vpn-${Date.now()}`
|
||||
// const orderId = `vpn-${Date.now()}-${user.id.slice(0, 8)}`
|
||||
|
||||
// Make API request to our backend
|
||||
const response = await fetch('/api/services/deploy-vpn', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
orderId,
|
||||
location: selectedLocation,
|
||||
plan: billingCycle,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
throw new Error(errorData.error || 'Failed to setup VPN service')
|
||||
}
|
||||
|
||||
const { data } = await response.json()
|
||||
|
||||
// Set the config content and show the config section
|
||||
setMockConfig(data.config)
|
||||
setShowConfig(true)
|
||||
} catch (err) {
|
||||
setError(err.message || 'Failed to setup VPN service')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const downloadConfig = () => {
|
||||
const blob = new Blob([mockConfig], { type: 'application/octet-stream' }) // force binary
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
// quote filename properly
|
||||
a.setAttribute('download', `siliconpin-vpn-${selectedLocation}.conf`)
|
||||
a.setAttribute('type', 'application/octet-stream')
|
||||
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
console.log('selectedLocation', selectedLocation)
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<Header />
|
||||
|
||||
<div className="container mx-auto px-4 pt-24 pb-16">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Page Header */}
|
||||
<div className="text-center mb-12">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full mb-4">
|
||||
<Shield className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold mb-4">WireGuard VPN</h1>
|
||||
<p className="text-xl text-muted-foreground">
|
||||
Secure, fast, and private VPN service with WireGuard protocol
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!user && (
|
||||
<Alert className="mb-8">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
You need to be logged in to setup VPN.{' '}
|
||||
<a href="/auth?redirect=/services/vpn" className="text-primary underline">
|
||||
Click here to login
|
||||
</a>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{!showConfig ? (
|
||||
<form onSubmit={handleSetupVPN}>
|
||||
<div className="space-y-8">
|
||||
{/* Billing Cycle */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Billing Plan</CardTitle>
|
||||
<CardDescription>Choose your preferred billing cycle</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs
|
||||
value={billingCycle}
|
||||
onValueChange={(v) => setBillingCycle(v as 'monthly' | 'yearly')}
|
||||
>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="monthly">Monthly - ₹{pricing.monthly}</TabsTrigger>
|
||||
<TabsTrigger value="yearly">
|
||||
Yearly - ₹{pricing.yearly} (Save 17%)
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Location Selection */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Select VPN Location</CardTitle>
|
||||
<CardDescription>
|
||||
Choose the server location for your VPN connection
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<RadioGroup value={selectedLocation} onValueChange={setSelectedLocation}>
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{locations.map((location) => (
|
||||
<div key={location.code} className="relative">
|
||||
<RadioGroupItem
|
||||
value={location.code}
|
||||
id={location.code}
|
||||
className="peer sr-only"
|
||||
/>
|
||||
<Label
|
||||
htmlFor={location.code}
|
||||
className={`flex items-center justify-between p-4 border-2 rounded-lg cursor-pointer transition-all hover:border-primary/50 peer-checked:border-primary peer-checked:bg-primary/5 ${
|
||||
location.popular ? 'border-primary/30' : 'border-border'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">{location.flag}</span>
|
||||
<div>
|
||||
<div className="font-medium">{location.name}</div>
|
||||
{location.popular && (
|
||||
<div className="text-xs text-primary font-medium">
|
||||
Most Popular
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<MapPin className="w-4 h-4 text-muted-foreground" />
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Order Summary */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Order Summary</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">WireGuard VPN</span>
|
||||
<span>₹{pricing[billingCycle]}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Billing Cycle</span>
|
||||
<span className="capitalize">{billingCycle}</span>
|
||||
</div>
|
||||
<div className="border-t pt-2">
|
||||
<div className="flex justify-between font-semibold">
|
||||
<span>Total</span>
|
||||
<span className="text-primary">₹{pricing[billingCycle]}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Your Balance</span>
|
||||
<span
|
||||
className={
|
||||
userBalance >= pricing[billingCycle]
|
||||
? 'text-green-600'
|
||||
: 'text-red-600'
|
||||
}
|
||||
>
|
||||
₹{userBalance.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Setup Button */}
|
||||
<Button type="submit" className="w-full" size="lg" disabled={loading || !user}>
|
||||
{loading ? 'Setting up VPN...' : 'Setup VPN Service'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
/* VPN Configuration */
|
||||
<div className="space-y-8">
|
||||
<Alert>
|
||||
<Shield className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
Your VPN service has been activated! Download the configuration file below.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>VPN Configuration</CardTitle>
|
||||
<CardDescription>
|
||||
Use this configuration with any WireGuard client
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Config File */}
|
||||
<div>
|
||||
<Label>Configuration File</Label>
|
||||
<div className="mt-2 p-4 bg-muted rounded-lg">
|
||||
<pre className="text-xs font-mono whitespace-pre-wrap text-muted-foreground">
|
||||
{mockConfig}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Buttons */}
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Button onClick={downloadConfig} className="flex-1">
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download Config File
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
onClick={() => alert('QR Code functionality would be implemented here')}
|
||||
>
|
||||
<QrCode className="w-4 h-4 mr-2" />
|
||||
Show QR Code
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Reset */}
|
||||
<div className="pt-4 border-t">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowConfig(false)}
|
||||
className="w-full"
|
||||
>
|
||||
Setup New VPN
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Setup Instructions */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Setup Instructions</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<h4 className="font-medium">For Desktop/Laptop:</h4>
|
||||
<ol className="text-sm space-y-1 list-decimal list-inside text-muted-foreground">
|
||||
<li>Download and install WireGuard client from wireguard.com</li>
|
||||
<li>Import the downloaded configuration file</li>
|
||||
<li>Click "Activate" to connect to the VPN</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<h4 className="font-medium">For Mobile:</h4>
|
||||
<ol className="text-sm space-y-1 list-decimal list-inside text-muted-foreground">
|
||||
<li>Install WireGuard app from App Store/Play Store</li>
|
||||
<li>Scan the QR code or import the config file</li>
|
||||
<li>Toggle the connection on</li>
|
||||
</ol>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Features */}
|
||||
{!showConfig && (
|
||||
<div className="mt-16 grid md:grid-cols-4 gap-6">
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-center">
|
||||
<Zap className="w-8 h-8 text-primary mx-auto mb-3" />
|
||||
<h4 className="font-semibold mb-2">Lightning Fast</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
WireGuard protocol for maximum speed
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-center">
|
||||
<Eye className="w-8 h-8 text-primary mx-auto mb-3" />
|
||||
<h4 className="font-semibold mb-2">No Logs Policy</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
We don't track or store your activity
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-center">
|
||||
<Lock className="w-8 h-8 text-primary mx-auto mb-3" />
|
||||
<h4 className="font-semibold mb-2">Strong Encryption</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
ChaCha20 encryption for security
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-center">
|
||||
<Globe className="w-8 h-8 text-primary mx-auto mb-3" />
|
||||
<h4 className="font-semibold mb-2">Multiple Locations</h4>
|
||||
<p className="text-sm text-muted-foreground">Servers across the globe</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user