feat: update CORS configuration and enhance player profile with Minecraft stats and tab navigation

This commit is contained in:
Lars Behrends
2025-12-30 12:41:12 +01:00
parent 5fcd5dbdcb
commit 1dcac99e73
5 changed files with 231 additions and 167 deletions

View File

@@ -19,6 +19,7 @@ const PlayerProfile: React.FC = () => {
const [isEditOrgOpen, setIsEditOrgOpen] = useState(false);
const [ownedProjects, setOwnedProjects] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState<'story' | 'stats' | 'projects'>('story');
// Is this the logged-in user's profile?
const isOwner = currentUser?.linkedPlayerUuid === player?.uuid;
@@ -140,7 +141,7 @@ const PlayerProfile: React.FC = () => {
console.log(player);
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">
<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
@@ -205,36 +206,36 @@ const PlayerProfile: React.FC = () => {
<div className="flex items-center gap-2 text-textMuted">
<Icons.Terminal className="w-4 h-4" />
<span className="font-mono text-textMain">
{player.stats?.statistics?.general?.["minecraft:play_time"]
? `${Math.round((player.stats.statistics.general["minecraft:play_time"] || 0) / 20 / 3600)}h`
: `${player.stats?.playtimeHours || 0}h`
{player.minecraftStats?.statistics?.general?.["minecraft:play_time"]
? `${Math.round((player.minecraftStats.statistics.general["minecraft:play_time"] || 0) / 20 / 3600)}h`
: `${player.minecraftStats?.playtimeHours || 0}h`
}
</span>
</div>
<div className="flex items-center gap-2 text-textMuted">
<Icons.Box className="w-4 h-4" />
<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>
</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">
<Icons.Shield className="w-4 h-4" />
<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>
</div>
<div className="flex items-center gap-2 text-textMuted">
<Icons.Coins className="w-4 h-4" />
<span className="font-mono text-textMain">
{player.stats.char.foodLevel}/20 Food
{player.minecraftStats.char.foodLevel}/20 Food
</span>
</div>
<div className="flex items-center gap-2 text-textMuted">
<Icons.Hammer className="w-4 h-4" />
<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>
</div>
</>
@@ -262,7 +263,7 @@ const PlayerProfile: React.FC = () => {
{playerOrg ? playerOrg.name.charAt(0) : <Icons.Map className="w-5 h-5 opacity-50" />}
</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">
{playerOrg ? (
<span className="group-hover:text-accentInfo transition-colors">{playerOrg.name}</span>
@@ -275,72 +276,205 @@ const PlayerProfile: React.FC = () => {
</div>
</div>
{/* Right Col: Story & Projects */}
<div className="lg:col-span-2 space-y-6">
{/* Story Section */}
<div className="bg-surface border border-border rounded-xl p-8 shadow-card min-h-[400px] relative">
<div className="flex items-center justify-between mb-6 border-b border-border pb-4">
<h2 className="text-lg font-semibold flex items-center gap-2">
Charakter-Journal
</h2>
<div className="flex items-center gap-3">
<span className="text-xs text-textMuted font-mono">Markdown</span>
{isOwner && (
<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.Edit className="w-3 h-3" /> Bearbeiten
</button>
)}
</div>
{/* Right Col: Tabs */}
<div className="lg:col-span-2">
{/* Tab Navigation */}
<div className="bg-surface border border-border rounded-xl shadow-card mb-6">
<div className="flex border-b border-border">
<button
onClick={() => setActiveTab('story')}
className={`flex-1 px-4 py-3 text-sm font-medium transition-colors ${
activeTab === 'story'
? 'text-accentInfo border-b-2 border-accentInfo bg-accentInfo/5'
: 'text-textMuted hover:text-textMain hover:bg-surfaceHighlight/50'
}`}
>
<Icons.Scroll className="w-4 h-4 inline mr-2" />
Charakter-Journal
</button>
{player.minecraftStats && (
<button
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 className="prose-custom text-sm">
{renderMarkdown(player.storyMarkdown || '')}
</div>
</div>
{/* Owned Projects Section */}
{ownedProjects.length > 0 && (
<div className="bg-surface border border-border rounded-xl p-6 shadow-card">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<Icons.ShoppingBag className="w-5 h-5 text-accentInfo" />
Unternehmen & Projekte ({ownedProjects.length})
</h3>
<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}</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>
{/* Tab Content */}
<div className="p-6">
{activeTab === 'story' && (
<div className="min-h-[400px]">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<span className="text-xs text-textMuted font-mono">Markdown</span>
{isOwner && (
<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.Edit className="w-3 h-3" /> Bearbeiten
</button>
)}
</div>
</div>
))}
</div>
<div className="prose-custom text-sm">
{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>