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 (
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 }) => (
);
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} />}
{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'}>}
);
}