Modernize frontend and centralize branding

This commit is contained in:
wfalkowski committed 2026-10-18 20:35:12 +00:00
1 parent bc4542d0fb
commit 2d61d80550
55 files changed
+1901 -14446

No files matched your search

+631
View File
@@ -0,0 +1,631 @@
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 (
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Icon className="w-4 h-4" style={{ color }} />
<span className="text-sm font-medium" style={{ color: colors.foreground }}>{label}</span>
</div>
<div className="px-3 py-1 rounded-lg text-sm font-bold" style={{ backgroundColor: `${color}20`, color }}>
{value} {unit}
</div>
</div>
<div className="relative h-3 rounded-full cursor-pointer" style={{ backgroundColor: colors.brightBlack }}>
<div className="absolute h-full rounded-full transition-all duration-150" style={{ width: `${percentage}%`, backgroundColor: color }} />
<input type="range" min={min} max={max} step={step} value={value} onChange={(e) => onChange(Number(e.target.value))} className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" />
<div className="absolute top-1/2 -translate-y-1/2 w-5 h-5 rounded-full border-2 transition-all duration-150 shadow-lg" style={{ left: `calc(${percentage}% - 10px)`, backgroundColor: colors.black, borderColor: color }} />
</div>
<div className="flex justify-between text-xs" style={{ color: colors.white }}>
<span>{min} {unit}</span>
<span>{max} {unit}</span>
</div>
</div>
);
};
const SectionCard = ({ title, icon: Icon, children, color = colors.blue }) => (
<div className="rounded-2xl p-6 border border-dash-border transition-colors duration-200 bg-white/[0.02] backdrop-blur-sm w-full">
<div className="flex items-center gap-3 mb-6 pb-4 border-b border-white/5">
<div className="p-2.5 rounded-xl shadow-lg shadow-black/20" style={{ backgroundColor: `${color}15` }}>
<Icon className="w-5 h-5" style={{ color }} />
</div>
<h3 className="text-lg font-bold tracking-tight" style={{ color: colors.brightWhite }}>
{title}
</h3>
</div>
{children}
</div>
);
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 (
<div className="w-full space-y-6">
<DashboardViewShell>
<PageHeaderBar
accentColor={colors.green}
icon={Plus}
title="Create new server"
description="Deploy your perfect server in seconds"
actions={
<div className="flex items-center gap-3">
{demoEligible && (
<ActionButton
type="button"
onClick={() => setIsDemo((prev) => !prev)}
variant={isDemo ? 'primary' : 'secondary'}
className="!px-4 !py-2 !min-h-0"
>
{isDemo ? 'Demo Mode ON' : 'Try Free Demo'}
</ActionButton>
)}
<div className="flex items-center gap-3 px-4 py-3 rounded-xl border border-white/10 bg-black/20">
<Coins className="w-5 h-5 shrink-0" style={{ color: colors.brightYellow }} />
<p className="font-bold tabular-nums text-brightYellow">{userCredits.toLocaleString()}</p>
</div>
</div>
}
/>
<div className={dashLayout.bodyPadding}>
<SectionCard title="Server Type" icon={Server} color={colors.blue}>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{serverTypes.map((type) => {
const Icon = type.icon;
const isSelected = serverType === type.id;
return (
<button key={type.id} type="button" onClick={() => { setServerType(type.id); setSelectedImage(''); setSelectedVersion(''); setCapacityResult(null); }} className="p-4 rounded-xl border-2 transition-colors duration-200 hover:bg-white/[0.03] text-left h-full" style={{ backgroundColor: isSelected ? `${type.color}15` : colors.brightBlack, borderColor: isSelected ? type.color : 'transparent' }}>
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg shrink-0" style={{ backgroundColor: `${type.color}20` }}>
<Icon className="w-5 h-5 shrink-0" style={{ color: type.color }} />
</div>
<div className="flex-1 min-w-0">
<p className="font-semibold truncate" style={{ color: colors.brightWhite }}>{type.name}</p>
<p className="text-xs truncate" style={{ color: colors.foreground }}>{type.description}</p>
</div>
{isSelected && <Check className="w-5 h-5 shrink-0" style={{ color: type.color }} />}
</div>
</button>
);
})}
</div>
</SectionCard>
<SectionCard title="Quick Presets" icon={Settings} color={colors.magenta}>
{isDemo ? (
<div className="p-4 rounded-xl border-2" style={{ backgroundColor: colors.brightBlack, borderColor: colors.yellow }}>
<p className="text-sm" style={{ color: colors.foreground }}>Presets disabled for demo servers (fixed specs).</p>
</div>
) : (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{presets.map((preset) => {
const Icon = preset.icon;
const isActive = isPresetActive(preset);
return (
<button key={preset.name} type="button" onClick={() => applyPreset(preset)} className="relative p-4 rounded-xl border-2 transition-all duration-200 hover:scale-105 h-full" style={{ backgroundColor: isActive ? `${preset.color}15` : colors.brightBlack, borderColor: isActive ? preset.color : 'transparent' }}>
{preset.popular && <div className="absolute -top-2 -right-2"><Star className="w-5 h-5" style={{ color: colors.brightYellow, fill: colors.brightYellow }} /></div>}
<Icon className="w-6 h-6 mx-auto mb-2" style={{ color: preset.color }} />
<p className="font-semibold text-sm text-center" style={{ color: colors.brightWhite }}>{preset.name}</p>
<p className="text-xs mt-1 text-center" style={{ color: colors.foreground }}>{preset.cpu}C • {preset.ram}GB • {preset.storage}GB</p>
</button>
);
})}
</div>
)}
</SectionCard>
<SectionCard title="Custom Configuration" icon={Cpu} color={colors.brightCyan}>
{isDemo ? (
<div className="p-4 rounded-xl border-2" style={{ backgroundColor: colors.brightBlack, borderColor: colors.yellow }}>
<p className="text-sm" style={{ color: colors.foreground }}>Custom configuration disabled for demo servers.</p>
</div>
) : (
<div className="space-y-6">
<Slider value={cpu} min={1} max={4} step={1} onChange={(v) => setCpu(v)} label="vCPU Cores" unit="vCPU" icon={Cpu} color={colors.blue} />
<Slider value={ram} min={1} max={12} step={1} onChange={(v) => setRam(v)} label="RAM" unit="GB" icon={MemoryStick} color={colors.brightGreen} />
<Slider value={storage} min={10} max={100} step={5} onChange={(v) => setStorage(v)} label="Storage" unit="GB" icon={HardDrive} color={colors.brightYellow} />
</div>
)}
</SectionCard>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<SectionCard title="Server Image" icon={Layers} color={colors.brightMagenta}>
<div className="space-y-4">
<Dropdown value={selectedImage} options={currentImages.map(i => ({ 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 && <Dropdown value={selectedVersion} options={selectedImageData?.versions.map(v => ({ value: v, label: v })) || []} onChange={setSelectedVersion} placeholder="Select Version" icon={Settings} />}
</div>
</SectionCard>
<SectionCard title="Region" icon={MapPin} color={colors.brightCyan}>
<div className="p-4 rounded-xl border-2 flex items-center gap-3 " style={{ backgroundColor: `${colors.brightCyan}15`, borderColor: colors.brightCyan }}>
<div className="text-2xl">🇵🇱</div>
<div className="flex-1"><p className="font-semibold" style={{ color: colors.brightWhite }}>Reda, Poland</p></div>
<Check className="w-5 h-5 ml-auto" style={{ color: colors.brightCyan }} />
</div>
</SectionCard>
</div>
<SectionCard title="Server Name" icon={Server} color={colors.blue}>
<input type="text" value={serverName} onChange={handleServerNameChange} className="w-full rounded-xl px-4 py-3 text-sm border-2 focus:outline-none" style={{ backgroundColor: colors.brightBlack, borderColor: serverNameError ? colors.red : (serverName ? colors.blue : 'transparent'), color: colors.brightWhite }} placeholder="My Awesome Server" />
{serverNameError && <p className="text-xs mt-1" style={{ color: colors.red }}>{serverNameError}</p>}
</SectionCard>
<div className="rounded-2xl p-6 border border-dash-border bg-white/[0.02] backdrop-blur-sm">
<h3 className="text-lg font-bold tracking-tight mb-4 pb-4 border-b border-white/5 flex items-center gap-2" style={{ color: colors.brightWhite }}>
<div className="p-2 rounded-xl shadow-lg shadow-black/20" style={{ backgroundColor: `${colors.primary}15` }}>
<Box className="w-5 h-5" style={{ color: colors.primary }} />
</div>
Server Summary
</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
{[
{ 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) => (
<div key={item.label} className="p-3 rounded-xl text-center border" style={{ backgroundColor: `${item.color}10`, borderColor: `${item.color}30` }}>
<p className="text-xs mb-1 font-medium" style={{ color: item.color }}>{item.label}</p>
<p className="font-bold text-sm" style={{ color: colors.brightWhite }}>{item.value}</p>
</div>
))}
</div>
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4 pt-4 border-t border-dash-border">
<div>
<p className="text-sm" style={{ color: colors.foreground }}>{isDemo ? 'Demo Server' : 'Monthly Price'}</p>
<p className="text-3xl font-bold" style={{ color: isDemo ? colors.brightYellow : (canCreate ? colors.brightYellow : colors.brightRed) }}>
{isDemo ? 'FREE' : `${calculateCurrencyValue(serverCost)} ${getCurrencySymbol()}`}
{!isDemo && <span className="text-sm font-normal">/mo</span>}
</p>
</div>
<ActionButton
onClick={handleSubmit}
disabled={creating || !serverName.trim() || serverNameError || !selectedImage || (!isDemo && !canCreate)}
variant="primary"
className="!px-4 !py-2 !min-h-0 sm:!min-h-0"
>
{creating ? <RefreshCw className="w-4 h-4 animate-spin" /> : <><Plus className="w-4 h-4" />{isDemo ? 'Create Demo Server' : 'Create Server'}</>}
</ActionButton>
</div>
</div>
</div>
</DashboardViewShell>
</div>
);
}