import React, { useState, useEffect, useCallback } from 'react'; import { Monitor, Gamepad2, Globe, Plus, Coins, Cpu, MemoryStick, HardDrive, Check, Zap, Crown, Award, Rocket, Server, Star, MapPin, Layers, Box, Settings, Database, RefreshCw } from "lucide-react"; import { colors } from '../../theme/colors'; import { dashLayout } from '../../theme/dashboardTokens'; import { DashboardViewShell, PageHeaderBar, ActionButton, Dropdown } from '../../components/dashboardPrimitives'; import { minecraftVersions } from '../../components/DashboardComponents'; import { API_BASE_URL } from '../../config/api'; const Slider = ({ value, min, max, step, onChange, label, unit, icon: Icon, color }) => { const percentage = ((value - min) / (max - min)) * 100; return (
{label}
{value} {unit}
onChange(Number(e.target.value))} className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" />
{min} {unit} {max} {unit}
); }; const SectionCard = ({ title, icon: Icon, children, color = colors.blue }) => (

{title}

{children}
); export default function ServerCreationForm({ onCreateServer, isCreating: externalIsCreating, userCredits = 5000, selectedCurrency = 'EUR', onCurrencyChange, wsClient, pricing = { price_cpu: 35.0, price_ram: 35.0, price_storage: 5.0, multiplier_vps: 1.0, multiplier_game: 0.8, multiplier_website: 1.5, multiplier_database: 1.3 } }) { const [serverName, setServerName] = useState(''); const [serverNameError, setServerNameError] = useState(''); const [serverType, setServerType] = useState('vps'); const [cpu, setCpu] = useState(2); const [ram, setRam] = useState(4); const [storage, setStorage] = useState(50); const [selectedImage, setSelectedImage] = useState(''); const [selectedVersion, setSelectedVersion] = useState(''); const [isCreating, setIsCreating] = useState(false); const [capacityResult, setCapacityResult] = useState(null); const [demoEligible, setDemoEligible] = useState(null); const [checkingDemoEligibility, setCheckingDemoEligibility] = useState(false); const [isDemo, setIsDemo] = useState(false); const [selectedJavaVersion, setSelectedJavaVersion] = useState('Java 21'); const [selectedLoader, setSelectedLoader] = useState('Paper'); // State for currencies fetched from backend const [currencies, setCurrencies] = useState([]); const checkCapacity = useCallback(async () => { if (!serverType || !cpu || !ram || !storage) return; // We don't need setCheckingCapacity(true) as it was removed from state setCapacityResult({ checking: true }); try { // Prepare server config for capacity check const serverConfig = { name: 'capacity-check', serverType: serverType, cpu: `${cpu} vCPU`, ram: `${ram}GB`, storage: `${storage}GB`, image: selectedImage || 'ubuntu', version: selectedVersion || 'latest' }; // Get token from localStorage or session const token = localStorage.getItem('anchorhost_token') || localStorage.getItem('jwtToken') || ''; // Make API call to check capacity const response = await fetch(`${API_BASE_URL}/api/capacity/check`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(serverConfig) }); if (response.ok) { const data = await response.json(); if (data.success) { setCapacityResult({ success: data.capacity_check.success, message: data.capacity_check.message, suitable_nodes: data.capacity_check.suitable_nodes, details: data.capacity_check.details, summary: data.capacity_check.summary, cluster: data.cluster_capacity }); } else { setCapacityResult({ success: false, message: data.error || 'Capacity check failed', checking: false }); } } else if (response.status === 401) { // Handle unauthorized setCapacityResult({ success: false, message: 'Authentication required. Please log in again.', checking: false }); } } catch (err) { console.error('Capacity check error:', err); setCapacityResult({ success: false, message: 'Could not connect to capacity API', checking: false }); } }, [serverType, cpu, ram, storage, selectedImage, selectedVersion]); // Fallback function to set default EUR-based currencies const setDefaultEuroBasedCurrencies = useCallback(() => { console.log('Setting default EUR-based currencies'); const defaultCurrencies = [ { code: 'EUR', name: 'Euro', symbol: '€', rate: 1.00 }, { code: 'USD', name: 'US Dollar', symbol: '$', rate: 1.08 }, { code: 'GBP', name: 'British Pound', symbol: '£', rate: 0.85 }, { code: 'PLN', name: 'Polish Złoty', symbol: 'zł', rate: 4.32 }, { code: 'PHP', name: 'Philippine Peso', symbol: '₱', rate: 60.50 }, { code: 'CZK', name: 'Czech Koruna', symbol: 'Kč', rate: 24.65 } ]; setCurrencies(defaultCurrencies); }, []); // Minecraft compatibility logic useEffect(() => { if (serverType === 'game' && selectedImage === 'minecraft' && selectedVersion) { // Determine major version const parts = selectedVersion.split('.'); if (parts.length >= 1) { const major = parseInt(parts[0]); const minor = parts.length >= 2 ? parseInt(parts[1]) : 0; const patch = parts.length >= 3 ? parseInt(parts[2]) : 0; // Java Version Logic if (major >= 26) { setSelectedJavaVersion('Java 25'); } else if (major === 1) { if (minor >= 21 || (minor === 20 && patch >= 5)) { setSelectedJavaVersion('Java 21'); } else if (minor >= 18) { setSelectedJavaVersion('Java 17'); } else if (minor >= 17) { setSelectedJavaVersion('Java 16'); } else if (minor >= 16) { setSelectedJavaVersion('Java 11'); } else if (minor >= 12) { setSelectedJavaVersion('Java 8'); } else { setSelectedJavaVersion('Java 8'); } } // Loader Logic if (selectedLoader === 'Fabric' && minor < 14) { setSelectedLoader('Forge'); } } } }, [selectedVersion, serverType, selectedImage, selectedLoader]); // Enforce demo server specs when isDemo is true useEffect(() => { if (isDemo) { if (cpu !== 2) setCpu(2); if (ram !== 3) setRam(3); if (storage !== 20) setStorage(20); } }, [isDemo, cpu, ram, storage]); // Auto-check capacity when resources change useEffect(() => { if (serverType && cpu && ram && storage) { const debounceTimer = setTimeout(() => { checkCapacity(); }, 1000); return () => clearTimeout(debounceTimer); } }, [serverType, cpu, ram, storage, selectedImage, checkCapacity]); // Fetch exchange rates from backend const fetchExchangeRates = useCallback(async () => { try { console.log('Fetching exchange rates from backend...'); const response = await fetch(`${API_BASE_URL}/api/exchange-rates`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (data.success && data.rates) { console.log('✅ Exchange rates fetched successfully:', data.rates); const backendCurrencies = [ { code: 'EUR', name: 'Euro', symbol: '€', rate: 1.00 }, { code: 'USD', name: 'US Dollar', symbol: '$', rate: data.rates.USD || 0 }, { code: 'GBP', name: 'British Pound', symbol: '£', rate: data.rates.GBP || 0 }, { code: 'PLN', name: 'Polish Złoty', symbol: 'zł', rate: data.rates.PLN || 0 }, { code: 'PHP', name: 'Philippine Peso', symbol: '₱', rate: data.rates.PHP || 0 }, { code: 'CZK', name: 'Czech Koruna', symbol: 'Kč', rate: data.rates.CZK || 0 } ]; const filteredCurrencies = backendCurrencies.filter(currency => currency.code === 'EUR' || currency.rate > 0 ); setCurrencies(filteredCurrencies); console.log('💱 Currency rates updated from API, base: EUR'); } else { console.warn('Failed to fetch exchange rates:', data.error); setDefaultEuroBasedCurrencies(); } } catch (error) { console.error('Error fetching exchange rates:', error); setDefaultEuroBasedCurrencies(); } }, [setDefaultEuroBasedCurrencies]); // Initialize with default EUR-based currencies on first render useEffect(() => { if (currencies.length === 0) { setDefaultEuroBasedCurrencies(); } }, [currencies.length, setDefaultEuroBasedCurrencies]); // Fetch exchange rates on component mount useEffect(() => { fetchExchangeRates(); const interval = setInterval(fetchExchangeRates, 3600000); // 1 hour return () => clearInterval(interval); }, [fetchExchangeRates]); const checkDemoEligibility = useCallback(() => { if (!wsClient || wsClient.readyState !== WebSocket.OPEN) { return; } setCheckingDemoEligibility(true); wsClient.send(JSON.stringify({ type: 'check_demo_eligibility' })); }, [wsClient]); // Check demo eligibility when WebSocket is available and connected useEffect(() => { if (!wsClient) return; const checkEligibility = () => { if (wsClient.readyState === WebSocket.OPEN && demoEligible === null && !checkingDemoEligibility) { checkDemoEligibility(); } }; if (wsClient.readyState === WebSocket.OPEN) { checkEligibility(); } wsClient.addEventListener('open', checkEligibility); return () => wsClient.removeEventListener('open', checkEligibility); }, [wsClient, demoEligible, checkingDemoEligibility, checkDemoEligibility]); // Handle demo eligibility response useEffect(() => { if (!wsClient) return; const handleMessage = (event) => { try { const data = JSON.parse(event.data); if (data.type === 'demo_eligibility') { setDemoEligible(data.eligible); setCheckingDemoEligibility(false); } } catch (e) {} }; wsClient.addEventListener('message', handleMessage); return () => wsClient.removeEventListener('message', handleMessage); }, [wsClient]); // Calculate currency value based on EUR const calculateCurrencyValue = (credits) => { const selectedCurrencyData = currencies.find(c => c.code === selectedCurrency); if (!selectedCurrencyData) return (credits / 100).toFixed(2); const eurValue = credits / 100; const rate = selectedCurrencyData.rate; return (eurValue * rate).toFixed(2); }; // Get currency symbol const getCurrencySymbol = () => { const currency = currencies.find(c => c.code === selectedCurrency); return currency ? currency.symbol : '€'; }; const serverTypes = [ { id: 'vps', name: 'VPS', icon: Monitor, description: 'Virtual Private Server', color: colors.blue }, { id: 'game', name: 'Game Server', icon: Gamepad2, description: 'Game Server Hosting', color: colors.brightRed }, { id: 'website', name: 'Website', icon: Globe, description: 'Web Hosting', color: colors.brightGreen }, { id: 'database', name: 'Database', icon: Database, description: 'Database Hosting', color: colors.magenta } ]; const presets = [ { name: "Starter", cpu: 1, ram: 1, storage: 10, icon: Rocket, color: colors.blue, tag: "Basic" }, { name: "Standard", cpu: 2, ram: 2, storage: 20, icon: Crown, color: colors.brightGreen, tag: "Popular", popular: true }, { name: "Pro", cpu: 3, ram: 4, storage: 40, icon: Award, color: colors.magenta, tag: "Pro" }, { name: "Enterprise", cpu: 4, ram: 8, storage: 60, icon: Zap, color: colors.brightYellow, tag: "Max" } ]; const flatMinecraftVersions = minecraftVersions.flatMap(v => v.versions); const images = { vps: [ { id: 'ubuntu', name: 'Ubuntu', versions: ['24.04', '22.04', '20.04', '18.04'], popular: true }, { id: 'debian', name: 'Debian', versions: ['13', '12', '11', '10', '9'] }, { id: 'alpine', name: 'Alpine', versions: ['3.22', '3.21', '3.20'] }, { id: 'rocky', name: 'Rocky Linux', versions: ['9', '8'] }, { id: 'openbsd', name: 'OpenBSD', versions: ['7.7', '7.6'] }, { id: 'windows', name: 'Windows Server', versions: ['2022', '2019'] } ], game: [ { id: 'minecraft', name: 'Minecraft', versions: flatMinecraftVersions, popular: true }, { id: 'fivem', name: 'FiveM', versions: ['latest'] }, { id: 'cs2', name: 'Counter-Strike 2', versions: ['latest'] }, { id: 'rust', name: 'Rust', versions: ['latest'] }, { id: 'hytale', name: 'Hytale', versions: ['latest'] }, { id: 'unturned', name: 'Unturned', versions: ['latest'] }, { id: 'zomboid', name: 'Project Zomboid', versions: ['latest', 'unstable'] }, { id: 'factorio', name: 'Factorio', versions: ['latest', 'stable'] }, { id: 'ark', name: 'ARK: Survival Evolved', versions: ['latest'] }, { id: 'valheim', name: 'Valheim', versions: ['latest'] } ], website: [ { id: 'nginx', name: 'Nginx', versions: ['latest', 'alpine'], popular: true }, { id: 'wordpress', name: 'WordPress', versions: ['latest', 'php8.2', 'php8.1'], popular: true, notice: 'Requires a separate MySQL/PostgreSQL database server.' }, { id: 'wordpress-sqlite', name: 'WordPress (SQLite)', versions: [ '6.9.4-php8.5-apache', '6.9.4-php8.4-apache', '6.9.4-php8.3-apache', '6.9.2-php8.5-apache', '6.9.1-php8.5-apache', '6.9.0-php8.5-apache', '6.8.3-php8.5-apache', '6.8.2-php8.4-apache', '6.7.2-php8.4-apache' ], popular: true }, { id: 'apache', name: 'Apache', versions: ['latest', '2.4'] }, { id: 'node', name: 'Node.js', versions: ['22', '20', '18'] } ], database: [ { id: 'mysql', name: 'MySQL', versions: ['8.0', '5.7'], popular: true }, { id: 'postgresql', name: 'PostgreSQL', versions: ['15', '14', '13'], popular: true }, { id: 'mongodb', name: 'MongoDB', versions: ['7.0', '6.0', '5.0'] }, { id: 'redis', name: 'Redis', versions: ['7.2', '7.0', '6.2'] } ] }; const currentImages = images[serverType] || []; const selectedImageData = currentImages.find(i => i.id === selectedImage); const calculateCost = () => { const cpuCost = cpu * (pricing.price_cpu || 35); const ramCost = ram * (pricing.price_ram || 35); const storageCost = storage * (pricing.price_storage || 5); const typeMultiplier = { 'vps': pricing.multiplier_vps || 1.0, 'game': pricing.multiplier_game || 0.8, 'website': pricing.multiplier_website || 1.5, 'database': pricing.multiplier_database || 1.3 }[serverType] || 1.0; return Math.round((cpuCost + ramCost + storageCost) * typeMultiplier); }; const serverCost = calculateCost(); const canCreate = userCredits >= serverCost && capacityResult?.success; const applyPreset = (p) => { if (isDemo) return; setCpu(p.cpu); setRam(p.ram); setStorage(p.storage); setTimeout(() => checkCapacity(), 100); }; const isPresetActive = (p) => cpu === p.cpu && ram === p.ram && storage === p.storage; const getDefaultPorts = (type, image) => { if (type === 'website') return [{ internal: 80, external: 0, protocol: 'TCP' }]; if (type === 'game') { const ports = { minecraft: 25565, fivem: 30120, cs2: 27015, rust: 28015, ark: 7777, valheim: 2456 }; return [{ internal: ports[image] || 27015, external: 0, protocol: 'TCP' }]; } if (type === 'database') { const ports = { mysql: 3306, postgresql: 5432, mongodb: 27017, redis: 6379 }; return [{ internal: ports[image] || 3306, external: 0, protocol: 'TCP' }]; } return [{ internal: 22, external: 0, protocol: 'TCP' }]; }; const validateServerName = (name) => { if (!name || !name.trim()) return 'Server name cannot be empty'; if (!/^[a-zA-Z\s]+$/.test(name)) return 'Server name can only contain letters and spaces.'; if (name.length > 50) return 'Server name must be 50 characters or less'; return ''; }; const handleServerNameChange = (e) => { const value = e.target.value; setServerName(value); setServerNameError(validateServerName(value)); }; const handleSubmit = async () => { const nameError = validateServerName(serverName); if (nameError) { setServerNameError(nameError); return; } if (!serverName.trim() || (!isDemo && !canCreate) || !selectedImage) return; setIsCreating(true); if (onCreateServer) { const serverConfig = { name: serverName, type: serverType, cpu: `${cpu} vCPU`, ram: `${ram}GB`, storage: `${storage}GB`, image: selectedImage, version: selectedVersion, ports: getDefaultPorts(serverType, selectedImage), is_demo: isDemo, java_version: selectedJavaVersion, loader: selectedLoader }; try { await onCreateServer(serverConfig); setTimeout(() => { setIsCreating(false); setServerName(''); setServerType('vps'); setCpu(2); setRam(4); setStorage(50); setSelectedImage(''); setSelectedVersion(''); setCapacityResult(null); }, 2000); } catch (error) { setIsCreating(false); } } }; const creating = externalIsCreating || isCreating; return (
{demoEligible && ( setIsDemo((prev) => !prev)} variant={isDemo ? 'primary' : 'secondary'} className="!px-4 !py-2 !min-h-0" > {isDemo ? 'Demo Mode ON' : 'Try Free Demo'} )}

{userCredits.toLocaleString()}

} />
{serverTypes.map((type) => { const Icon = type.icon; const isSelected = serverType === type.id; return ( ); })}
{isDemo ? (

Presets disabled for demo servers (fixed specs).

) : (
{presets.map((preset) => { const Icon = preset.icon; const isActive = isPresetActive(preset); return ( ); })}
)}
{isDemo ? (

Custom configuration disabled for demo servers.

) : (
setCpu(v)} label="vCPU Cores" unit="vCPU" icon={Cpu} color={colors.blue} /> setRam(v)} label="RAM" unit="GB" icon={MemoryStick} color={colors.brightGreen} /> setStorage(v)} label="Storage" unit="GB" icon={HardDrive} color={colors.brightYellow} />
)}
({ value: i.id, label: i.name }))} onChange={(v) => { setSelectedImage(v); setSelectedVersion(currentImages.find(i => i.id === v)?.versions[0] || ''); }} placeholder="Server Image" icon={Layers} /> {selectedImage && ({ value: v, label: v })) || []} onChange={setSelectedVersion} placeholder="Select Version" icon={Settings} />}
🇵🇱

Reda, Poland

{serverNameError &&

{serverNameError}

}

Server Summary

{[ { label: 'CPU', value: `${cpu} vCPU`, color: colors.blue }, { label: 'RAM', value: `${ram} GB`, color: colors.brightGreen }, { label: 'Storage', value: `${storage} GB`, color: colors.brightYellow }, { label: 'Region', value: 'Reda, PL', color: colors.brightCyan } ].map((item) => (

{item.label}

{item.value}

))}

{isDemo ? 'Demo Server' : 'Monthly Price'}

{isDemo ? 'FREE' : `${calculateCurrencyValue(serverCost)} ${getCurrencySymbol()}`} {!isDemo && /mo}

{creating ? : <>{isDemo ? 'Create Demo Server' : 'Create Server'}}
); }