mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
feat: update CORS configuration and enhance player profile with Minecraft stats and tab navigation
This commit is contained in:
@@ -18,6 +18,11 @@ const CALLBACK_URL = process.env.CALLBACK_URL || 'http://localhost:3000/auth/dis
|
|||||||
const FRONTEND_URL = process.env.FRONTEND_URL || 'http://localhost:8000';
|
const FRONTEND_URL = process.env.FRONTEND_URL || 'http://localhost:8000';
|
||||||
const BACKEND_URL = process.env.BACKEND_URL || 'https://vollidioten.ceraticsoft.de';
|
const BACKEND_URL = process.env.BACKEND_URL || 'https://vollidioten.ceraticsoft.de';
|
||||||
|
|
||||||
|
// CORS configuration - allow multiple origins for development
|
||||||
|
const corsOrigins = process.env.NODE_ENV === 'production'
|
||||||
|
? [FRONTEND_URL]
|
||||||
|
: [FRONTEND_URL, 'http://localhost:3000', 'http://localhost:8000', 'http://127.0.0.1:3000'];
|
||||||
|
|
||||||
// File upload configuration
|
// File upload configuration
|
||||||
const UPLOAD_DIR = path.join(__dirname, 'uploads');
|
const UPLOAD_DIR = path.join(__dirname, 'uploads');
|
||||||
if (!fs.existsSync(UPLOAD_DIR)) {
|
if (!fs.existsSync(UPLOAD_DIR)) {
|
||||||
@@ -55,7 +60,7 @@ init(); // Initialize DB
|
|||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(cors({ origin: FRONTEND_URL, credentials: true }));
|
app.use(cors({ origin: corsOrigins, credentials: true }));
|
||||||
app.use(session({
|
app.use(session({
|
||||||
secret: process.env.SESSION_SECRET || 'dev_secret',
|
secret: process.env.SESSION_SECRET || 'dev_secret',
|
||||||
resave: false,
|
resave: false,
|
||||||
|
|||||||
@@ -72,21 +72,23 @@ const Layout: React.FC<LayoutProps> = ({ children, activeTab, onNavigate }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<button
|
{user?.isAdmin && (
|
||||||
onClick={() => onNavigate('datapack')}
|
<>
|
||||||
className="hidden md:flex items-center gap-2 text-xs font-medium text-textMain hover:text-accentInfo transition-colors"
|
<button
|
||||||
>
|
onClick={() => onNavigate('datapack')}
|
||||||
<Icons.Box className="w-3 h-3" />
|
className="hidden md:flex items-center gap-2 text-xs font-medium text-textMain hover:text-accentInfo transition-colors"
|
||||||
<span>Datapack holen</span>
|
>
|
||||||
</button>
|
<Icons.Box className="w-3 h-3" />
|
||||||
<button
|
<span>Datapack holen</span>
|
||||||
onClick={() => onNavigate('setup')}
|
</button><button
|
||||||
className="hidden md:flex items-center gap-2 text-xs font-medium text-textMuted hover:text-accentInfo transition-colors border border-border rounded-full px-4 py-1.5 hover:bg-surfaceHighlight"
|
onClick={() => onNavigate('setup')}
|
||||||
>
|
className="hidden md:flex items-center gap-2 text-xs font-medium text-textMuted hover:text-accentInfo transition-colors border border-border rounded-full px-4 py-1.5 hover:bg-surfaceHighlight"
|
||||||
|
>
|
||||||
<Icons.Terminal className="w-3 h-3" />
|
<Icons.Terminal className="w-3 h-3" />
|
||||||
<span>Admin Setup</span>
|
<span>Admin Setup</span>
|
||||||
</button>
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{/* Mobile Menu Toggle */}
|
{/* Mobile Menu Toggle */}
|
||||||
<button
|
<button
|
||||||
className="md:hidden text-textMuted hover:text-textMain"
|
className="md:hidden text-textMuted hover:text-textMain"
|
||||||
|
|||||||
@@ -647,7 +647,7 @@ const EditNpcCompanyCard: React.FC<{ company: any; npcCitizens: any[]; onUpdate:
|
|||||||
|
|
||||||
const AdminPage: React.FC<AdminPageProps> = ({ onBack }) => {
|
const AdminPage: React.FC<AdminPageProps> = ({ onBack }) => {
|
||||||
const [user, setUser] = useState(authService.getUser());
|
const [user, setUser] = useState(authService.getUser());
|
||||||
const [activeTab, setActiveTab] = useState<'overview' | 'npcs' | 'create-npc' | 'edit-npcs' | 'cities' | 'create-city' | 'manage-admins'>('overview');
|
const [activeTab, setActiveTab] = useState<'overview' | 'create-npc' | 'edit-npcs' | 'cities' | 'create-city' | 'manage-admins'>('overview');
|
||||||
const [npcs, setNpcs] = useState<any>({ citizens: [], companies: [] });
|
const [npcs, setNpcs] = useState<any>({ citizens: [], companies: [] });
|
||||||
const [cities, setCities] = useState<any[]>([]);
|
const [cities, setCities] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -903,17 +903,11 @@ const AdminPage: React.FC<AdminPageProps> = ({ onBack }) => {
|
|||||||
>
|
>
|
||||||
Übersicht
|
Übersicht
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
onClick={() => setActiveTab('npcs')}
|
|
||||||
className={`px-6 py-3 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${activeTab === 'npcs' ? 'border-accentInfo text-white' : 'border-transparent text-textMuted hover:text-white'}`}
|
|
||||||
>
|
|
||||||
NPCs verwalten ({npcs.citizens.length + npcs.companies.length})
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('edit-npcs')}
|
onClick={() => setActiveTab('edit-npcs')}
|
||||||
className={`px-6 py-3 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${activeTab === 'edit-npcs' ? 'border-accentInfo text-white' : 'border-transparent text-textMuted hover:text-white'}`}
|
className={`px-6 py-3 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${activeTab === 'edit-npcs' ? 'border-accentInfo text-white' : 'border-transparent text-textMuted hover:text-white'}`}
|
||||||
>
|
>
|
||||||
NPCs bearbeiten
|
NPCs verwalten ({npcs.citizens.length + npcs.companies.length})
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('create-npc')}
|
onClick={() => setActiveTab('create-npc')}
|
||||||
@@ -987,79 +981,6 @@ const AdminPage: React.FC<AdminPageProps> = ({ onBack }) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'npcs' && (
|
|
||||||
<div className="space-y-8">
|
|
||||||
{/* NPC Citizens */}
|
|
||||||
<div className="bg-surface border border-border rounded-xl p-6">
|
|
||||||
<h3 className="text-xl font-bold mb-4 flex items-center gap-2">
|
|
||||||
<Icons.Users className="w-5 h-5 text-blue-400" />
|
|
||||||
NPC-Bürger ({npcs.citizens.length})
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
{loading ? (
|
|
||||||
<div className="flex justify-center py-8">
|
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-accentInfo"></div>
|
|
||||||
</div>
|
|
||||||
) : npcs.citizens.length === 0 ? (
|
|
||||||
<p className="text-textMuted">Keine NPC-Bürger vorhanden.</p>
|
|
||||||
) : (
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
{npcs.citizens.map((citizen: any) => (
|
|
||||||
<div key={citizen.uuid} className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
|
||||||
<div className="flex items-center gap-3 mb-2">
|
|
||||||
<div className="w-8 h-8 bg-purple-500/20 rounded flex items-center justify-center text-xs font-bold text-purple-400">
|
|
||||||
NPC
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-white">{citizen.username}</h4>
|
|
||||||
<p className="text-xs text-textMuted">{citizen.stats.role}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-textMuted">
|
|
||||||
Level {citizen.stats.level} • {citizen.stats.playtimeHours}h Spielzeit
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* NPC Companies */}
|
|
||||||
<div className="bg-surface border border-border rounded-xl p-6">
|
|
||||||
<h3 className="text-xl font-bold mb-4 flex items-center gap-2">
|
|
||||||
<Icons.ShoppingBag className="w-5 h-5 text-purple-400" />
|
|
||||||
NPC-Firmen ({npcs.companies.length})
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
{npcs.companies.length === 0 ? (
|
|
||||||
<p className="text-textMuted">Keine NPC-Firmen vorhanden.</p>
|
|
||||||
) : (
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
{npcs.companies.map((company: any) => (
|
|
||||||
<div key={company.id} className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
|
||||||
<div className="flex justify-between items-start mb-2">
|
|
||||||
<h4 className="font-medium text-white">{company.title}</h4>
|
|
||||||
<span className="text-xs px-2 py-1 bg-purple-500/20 text-purple-400 rounded">
|
|
||||||
{company.category}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-textMuted mb-2">{company.description}</p>
|
|
||||||
<div className="text-xs text-textMuted">
|
|
||||||
Eigentümer: {company.owner} • Gegründet: {company.foundedDate}
|
|
||||||
</div>
|
|
||||||
{company.shopCatalog && company.shopCatalog.length > 0 && (
|
|
||||||
<div className="mt-2 text-xs text-accentInfo">
|
|
||||||
Shop: {company.shopCatalog.length} Artikel
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeTab === 'edit-npcs' && (
|
{activeTab === 'edit-npcs' && (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{/* Edit NPC Citizens */}
|
{/* Edit NPC Citizens */}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const PlayerProfile: React.FC = () => {
|
|||||||
const [isEditOrgOpen, setIsEditOrgOpen] = useState(false);
|
const [isEditOrgOpen, setIsEditOrgOpen] = useState(false);
|
||||||
const [ownedProjects, setOwnedProjects] = useState<any[]>([]);
|
const [ownedProjects, setOwnedProjects] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [activeTab, setActiveTab] = useState<'story' | 'stats' | 'projects'>('story');
|
||||||
|
|
||||||
// Is this the logged-in user's profile?
|
// Is this the logged-in user's profile?
|
||||||
const isOwner = currentUser?.linkedPlayerUuid === player?.uuid;
|
const isOwner = currentUser?.linkedPlayerUuid === player?.uuid;
|
||||||
@@ -140,7 +141,7 @@ const PlayerProfile: React.FC = () => {
|
|||||||
|
|
||||||
console.log(player);
|
console.log(player);
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto animate-in slide-in-from-right-4 duration-300">
|
<div className="max-w-8xl mx-auto animate-in slide-in-from-right-4 duration-300">
|
||||||
<div className="flex justify-between items-center mb-6">
|
<div className="flex justify-between items-center mb-6">
|
||||||
<button onClick={() => navigate('/players')} className="flex items-center gap-2 text-sm text-textMuted hover:text-textMain transition-colors">
|
<button onClick={() => navigate('/players')} className="flex items-center gap-2 text-sm text-textMuted hover:text-textMain transition-colors">
|
||||||
<span className="text-lg">←</span> Zurück zur Liste
|
<span className="text-lg">←</span> Zurück zur Liste
|
||||||
@@ -205,36 +206,36 @@ const PlayerProfile: React.FC = () => {
|
|||||||
<div className="flex items-center gap-2 text-textMuted">
|
<div className="flex items-center gap-2 text-textMuted">
|
||||||
<Icons.Terminal className="w-4 h-4" />
|
<Icons.Terminal className="w-4 h-4" />
|
||||||
<span className="font-mono text-textMain">
|
<span className="font-mono text-textMain">
|
||||||
{player.stats?.statistics?.general?.["minecraft:play_time"]
|
{player.minecraftStats?.statistics?.general?.["minecraft:play_time"]
|
||||||
? `${Math.round((player.stats.statistics.general["minecraft:play_time"] || 0) / 20 / 3600)}h`
|
? `${Math.round((player.minecraftStats.statistics.general["minecraft:play_time"] || 0) / 20 / 3600)}h`
|
||||||
: `${player.stats?.playtimeHours || 0}h`
|
: `${player.minecraftStats?.playtimeHours || 0}h`
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-textMuted">
|
<div className="flex items-center gap-2 text-textMuted">
|
||||||
<Icons.Box className="w-4 h-4" />
|
<Icons.Box className="w-4 h-4" />
|
||||||
<span className="font-mono text-textMain">
|
<span className="font-mono text-textMain">
|
||||||
Lvl {player.stats?.char?.xpLevel || player.stats?.level || 1}
|
Lvl {player.minecraftStats?.char?.xpLevel || player.minecraftStats?.level || 1}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{player.stats && player.stats.char && player.stats.statistics && (
|
{player.minecraftStats && player.minecraftStats.char && player.minecraftStats.statistics && (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-2 text-textMuted">
|
<div className="flex items-center gap-2 text-textMuted">
|
||||||
<Icons.Shield className="w-4 h-4" />
|
<Icons.Shield className="w-4 h-4" />
|
||||||
<span className="font-mono text-textMain">
|
<span className="font-mono text-textMain">
|
||||||
{player.stats.char.health}/{player.stats.char.maxHealth} HP
|
{player.minecraftStats.char.health}/{player.minecraftStats.char.maxHealth} HP
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-textMuted">
|
<div className="flex items-center gap-2 text-textMuted">
|
||||||
<Icons.Coins className="w-4 h-4" />
|
<Icons.Coins className="w-4 h-4" />
|
||||||
<span className="font-mono text-textMain">
|
<span className="font-mono text-textMain">
|
||||||
{player.stats.char.foodLevel}/20 Food
|
{player.minecraftStats.char.foodLevel}/20 Food
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-textMuted">
|
<div className="flex items-center gap-2 text-textMuted">
|
||||||
<Icons.Hammer className="w-4 h-4" />
|
<Icons.Hammer className="w-4 h-4" />
|
||||||
<span className="font-mono text-textMain">
|
<span className="font-mono text-textMain">
|
||||||
{player.stats.statistics.general?.["minecraft:mob_kills"] || 0} Kills
|
{player.minecraftStats.statistics.general?.["minecraft:mob_kills"] || 0} Kills
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -262,7 +263,7 @@ const PlayerProfile: React.FC = () => {
|
|||||||
{playerOrg ? playerOrg.name.charAt(0) : <Icons.Map className="w-5 h-5 opacity-50" />}
|
{playerOrg ? playerOrg.name.charAt(0) : <Icons.Map className="w-5 h-5 opacity-50" />}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium text-textMain">{player.stats?.role || 'Unbekannt'}</div>
|
<div className="text-sm font-medium text-textMain">{player.minecraftStats?.role || 'Unbekannt'}</div>
|
||||||
<div className="text-xs text-textMuted">
|
<div className="text-xs text-textMuted">
|
||||||
{playerOrg ? (
|
{playerOrg ? (
|
||||||
<span className="group-hover:text-accentInfo transition-colors">{playerOrg.name}</span>
|
<span className="group-hover:text-accentInfo transition-colors">{playerOrg.name}</span>
|
||||||
@@ -275,72 +276,205 @@ const PlayerProfile: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Col: Story & Projects */}
|
{/* Right Col: Tabs */}
|
||||||
<div className="lg:col-span-2 space-y-6">
|
<div className="lg:col-span-2">
|
||||||
{/* Story Section */}
|
{/* Tab Navigation */}
|
||||||
<div className="bg-surface border border-border rounded-xl p-8 shadow-card min-h-[400px] relative">
|
<div className="bg-surface border border-border rounded-xl shadow-card mb-6">
|
||||||
<div className="flex items-center justify-between mb-6 border-b border-border pb-4">
|
<div className="flex border-b border-border">
|
||||||
<h2 className="text-lg font-semibold flex items-center gap-2">
|
<button
|
||||||
Charakter-Journal
|
onClick={() => setActiveTab('story')}
|
||||||
</h2>
|
className={`flex-1 px-4 py-3 text-sm font-medium transition-colors ${
|
||||||
<div className="flex items-center gap-3">
|
activeTab === 'story'
|
||||||
<span className="text-xs text-textMuted font-mono">Markdown</span>
|
? 'text-accentInfo border-b-2 border-accentInfo bg-accentInfo/5'
|
||||||
{isOwner && (
|
: 'text-textMuted hover:text-textMain hover:bg-surfaceHighlight/50'
|
||||||
<button
|
}`}
|
||||||
onClick={() => setIsEditStoryOpen(true)}
|
>
|
||||||
className="flex items-center gap-1.5 text-xs bg-surfaceHighlight hover:bg-white/10 border border-white/10 px-2 py-1 rounded transition-colors text-white"
|
<Icons.Scroll className="w-4 h-4 inline mr-2" />
|
||||||
>
|
Charakter-Journal
|
||||||
<Icons.Edit className="w-3 h-3" /> Bearbeiten
|
</button>
|
||||||
</button>
|
{player.minecraftStats && (
|
||||||
)}
|
<button
|
||||||
</div>
|
onClick={() => setActiveTab('stats')}
|
||||||
|
className={`flex-1 px-4 py-3 text-sm font-medium transition-colors ${
|
||||||
|
activeTab === 'stats'
|
||||||
|
? 'text-accentInfo border-b-2 border-accentInfo bg-accentInfo/5'
|
||||||
|
: 'text-textMuted hover:text-textMain hover:bg-surfaceHighlight/50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Icons.Terminal className="w-4 h-4 inline mr-2" />
|
||||||
|
Minecraft Stats
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{ownedProjects.length > 0 && (
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('projects')}
|
||||||
|
className={`flex-1 px-4 py-3 text-sm font-medium transition-colors ${
|
||||||
|
activeTab === 'projects'
|
||||||
|
? 'text-accentInfo border-b-2 border-accentInfo bg-accentInfo/5'
|
||||||
|
: 'text-textMuted hover:text-textMain hover:bg-surfaceHighlight/50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Icons.ShoppingBag className="w-4 h-4 inline mr-2" />
|
||||||
|
Projekte ({ownedProjects.length})
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="prose-custom text-sm">
|
|
||||||
{renderMarkdown(player.storyMarkdown || '')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Owned Projects Section */}
|
{/* Tab Content */}
|
||||||
{ownedProjects.length > 0 && (
|
<div className="p-6">
|
||||||
<div className="bg-surface border border-border rounded-xl p-6 shadow-card">
|
{activeTab === 'story' && (
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
<div className="min-h-[400px]">
|
||||||
<Icons.ShoppingBag className="w-5 h-5 text-accentInfo" />
|
<div className="flex items-center justify-between mb-6">
|
||||||
Unternehmen & Projekte ({ownedProjects.length})
|
<div className="flex items-center gap-3">
|
||||||
</h3>
|
<span className="text-xs text-textMuted font-mono">Markdown</span>
|
||||||
<div className="grid grid-cols-1 gap-4">
|
{isOwner && (
|
||||||
{ownedProjects.map((project) => (
|
<button
|
||||||
<div key={project.id} className="bg-surfaceHighlight/30 border border-border rounded-lg p-4 hover:border-accentInfo/50 transition-all">
|
onClick={() => setIsEditStoryOpen(true)}
|
||||||
<div className="flex justify-between items-start mb-2">
|
className="flex items-center gap-1.5 text-xs bg-surfaceHighlight hover:bg-white/10 border border-white/10 px-2 py-1 rounded transition-colors text-white"
|
||||||
<h4 className="font-medium text-textMain">{project.title}</h4>
|
>
|
||||||
<span className={`text-xs px-2 py-1 rounded border ${
|
<Icons.Edit className="w-3 h-3" /> Bearbeiten
|
||||||
project.status === 'active' ? 'bg-green-500/10 text-green-400 border-green-500/20' :
|
</button>
|
||||||
project.status === 'recruiting' ? 'bg-blue-500/10 text-blue-400 border-blue-500/20' :
|
)}
|
||||||
'bg-gray-500/10 text-gray-400 border-gray-500/20'
|
|
||||||
}`}>
|
|
||||||
{project.category}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-textMuted mb-3 line-clamp-2">{project.description}</p>
|
|
||||||
<div className="flex items-center justify-between text-xs text-textMuted">
|
|
||||||
<span>Gegründet: {project.foundedDate}</span>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{project.shopCatalog && project.shopCatalog.length > 0 && (
|
|
||||||
<span className="flex items-center gap-1 text-accentInfo">
|
|
||||||
<Icons.ShoppingBag className="w-3 h-3" />
|
|
||||||
Shop ({project.shopCatalog.length})
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span className="flex items-center gap-1">
|
|
||||||
<Icons.Users className="w-3 h-3" />
|
|
||||||
{project.employees.length + 1}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
<div className="prose-custom text-sm">
|
||||||
</div>
|
{renderMarkdown(player.storyMarkdown || '')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'stats' && player.minecraftStats && (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Last Sync */}
|
||||||
|
<div className="p-3 bg-surfaceHighlight/30 rounded-lg border border-border">
|
||||||
|
<div className="text-sm text-textMuted mb-1">Letzte Synchronisation</div>
|
||||||
|
<div className="text-sm font-mono text-textMain">
|
||||||
|
{new Date(player.minecraftStats.lastSync).toLocaleString('de-DE', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Statistics */}
|
||||||
|
{player.minecraftStats.statistics && (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* General Stats */}
|
||||||
|
{player.minecraftStats.statistics.general && Object.keys(player.minecraftStats.statistics.general).length > 0 && (
|
||||||
|
<div>
|
||||||
|
<h4 className="text-md font-semibold mb-3 text-textMain">Allgemein</h4>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||||
|
{Object.entries(player.minecraftStats.statistics.general)
|
||||||
|
.sort(([, a], [, b]) => (b as number) - (a as number))
|
||||||
|
.map(([key, value]) => (
|
||||||
|
<div key={key} className="flex justify-between items-center p-2 bg-surfaceHighlight/20 rounded border border-white/5">
|
||||||
|
<span className="text-sm text-textMuted font-mono">{key.replace('minecraft:', '')}</span>
|
||||||
|
<span className="text-sm font-bold text-textMain">{value.toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Kills */}
|
||||||
|
{player.minecraftStats.statistics.kills && Object.keys(player.minecraftStats.statistics.kills).length > 0 && (
|
||||||
|
<div>
|
||||||
|
<h4 className="text-md font-semibold mb-3 text-textMain">Kills</h4>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||||
|
{Object.entries(player.minecraftStats.statistics.kills)
|
||||||
|
.sort(([, a], [, b]) => (b as number) - (a as number))
|
||||||
|
.map(([key, value]) => (
|
||||||
|
<div key={key} className="flex justify-between items-center p-2 bg-surfaceHighlight/20 rounded border border-white/5">
|
||||||
|
<span className="text-sm text-textMuted font-mono">{key.replace('minecraft:', '')}</span>
|
||||||
|
<span className="text-sm font-bold text-textMain">{value}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Killed By */}
|
||||||
|
{player.minecraftStats.statistics.killed_by && Object.keys(player.minecraftStats.statistics.killed_by).length > 0 && (
|
||||||
|
<div>
|
||||||
|
<h4 className="text-md font-semibold mb-3 text-textMain">Gestorben durch</h4>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||||
|
{Object.entries(player.minecraftStats.statistics.killed_by)
|
||||||
|
.sort(([, a], [, b]) => (b as number) - (a as number))
|
||||||
|
.map(([key, value]) => (
|
||||||
|
<div key={key} className="flex justify-between items-center p-2 bg-surfaceHighlight/20 rounded border border-white/5">
|
||||||
|
<span className="text-sm text-textMuted font-mono">{key.replace('minecraft:', '')}</span>
|
||||||
|
<span className="text-sm font-bold text-textMain">{value}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Advancements */}
|
||||||
|
{player.minecraftStats.advancements && player.minecraftStats.advancements.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<h4 className="text-md font-semibold mb-3 text-textMain">Erfolge ({player.minecraftStats.advancements.length})</h4>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||||
|
{player.minecraftStats.advancements.map((advancement) => (
|
||||||
|
<div key={advancement.id} className="p-3 bg-surfaceHighlight/20 rounded border border-white/5">
|
||||||
|
<div className="text-sm font-medium text-textMain">{advancement.title}</div>
|
||||||
|
<div className="text-xs text-textMuted font-mono">{advancement.id.replace('minecraft:', '')}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'projects' && ownedProjects.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
{ownedProjects.map((project) => (
|
||||||
|
<div key={project.id} className="bg-surfaceHighlight/30 border border-border rounded-lg p-4 hover:border-accentInfo/50 transition-all">
|
||||||
|
<div className="flex justify-between items-start mb-2">
|
||||||
|
<h4 className="font-medium text-textMain">{project.title}</h4>
|
||||||
|
<span className={`text-xs px-2 py-1 rounded border ${
|
||||||
|
project.status === 'active' ? 'bg-green-500/10 text-green-400 border-green-500/20' :
|
||||||
|
project.status === 'recruiting' ? 'bg-blue-500/10 text-blue-400 border-blue-500/20' :
|
||||||
|
'bg-gray-500/10 text-gray-400 border-gray-500/20'
|
||||||
|
}`}>
|
||||||
|
{project.category}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-textMuted mb-3 line-clamp-2">
|
||||||
|
{project.description.split(' ').length > 50
|
||||||
|
? project.description.split(' ').slice(0, 50).join(' ') + '...'
|
||||||
|
: project.description}
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center justify-between text-xs text-textMuted">
|
||||||
|
<span>Gegründet: {project.foundedDate}</span>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{project.shopCatalog && project.shopCatalog.length > 0 && (
|
||||||
|
<span className="flex items-center gap-1 text-accentInfo">
|
||||||
|
<Icons.ShoppingBag className="w-3 h-3" />
|
||||||
|
Shop ({project.shopCatalog.length})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Icons.Users className="w-3 h-3" />
|
||||||
|
{project.employees.length + 1}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ const VentureCard = ({ project, onClick }: { project: Project, onClick?: () => v
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-textMuted mb-6 flex-1 leading-relaxed relative z-10">
|
<p className="text-sm text-textMuted mb-6 flex-1 leading-relaxed relative z-10">
|
||||||
{project.description}
|
{project.description.split(' ').length > 50
|
||||||
|
? project.description.split(' ').slice(0, 50).join(' ') + '...'
|
||||||
|
: project.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="pt-4 border-t border-white/5 space-y-3 relative z-10">
|
<div className="pt-4 border-t border-white/5 space-y-3 relative z-10">
|
||||||
|
|||||||
Reference in New Issue
Block a user