mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
feat: Initialize project with Vite, React, and TypeScript
Sets up the foundational structure for the Obsidian | RP Plattform. This includes configuring Vite as the build tool, integrating React for the UI, and establishing TypeScript for type safety. Also includes initial styling and placeholder data to define the application's core interfaces.
This commit is contained in:
247
pages/CityProfile.tsx
Normal file
247
pages/CityProfile.tsx
Normal file
@@ -0,0 +1,247 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Organization, Project, Player } from '../types';
|
||||
import { MOCK_PLAYERS, MOCK_PROJECTS } from '../constants';
|
||||
import { Icons } from '../components/IconSet';
|
||||
|
||||
interface CityProfileProps {
|
||||
city: Organization;
|
||||
onBack: () => void;
|
||||
backLabel?: string;
|
||||
onSelectPlayer: (id: string) => void;
|
||||
onSelectProject: (id: string) => void;
|
||||
}
|
||||
|
||||
const CityProfile: React.FC<CityProfileProps> = ({
|
||||
city,
|
||||
onBack,
|
||||
backLabel = 'Zurück',
|
||||
onSelectPlayer,
|
||||
onSelectProject
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState<'overview' | 'residents' | 'ventures'>('overview');
|
||||
|
||||
const residents = MOCK_PLAYERS.filter(p => p.stats.organizationId === city.id);
|
||||
const ventures = MOCK_PROJECTS.filter(p => p.associatedOrgId === city.id);
|
||||
|
||||
return (
|
||||
<div className="animate-in slide-in-from-right-4 duration-300">
|
||||
<button onClick={onBack} className="flex items-center gap-2 text-sm text-textMuted hover:text-textMain mb-6 transition-colors group">
|
||||
<span className="group-hover:-translate-x-1 transition-transform">←</span> {backLabel}
|
||||
</button>
|
||||
|
||||
{/* Hero Header */}
|
||||
<div className="relative h-64 md:h-80 rounded-2xl overflow-hidden border border-border mb-8 shadow-card">
|
||||
<img
|
||||
src={city.bannerUrl || 'https://images.unsplash.com/photo-1562774053-701939374585?q=80&w=2086&auto=format&fit=crop'}
|
||||
alt={city.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/50 to-transparent" />
|
||||
|
||||
<div className="absolute bottom-0 left-0 right-0 p-8">
|
||||
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4">
|
||||
<div>
|
||||
{city.establishedYear && (
|
||||
<div className="text-accentInfo font-mono text-xs mb-2 bg-black/40 inline-block px-2 py-1 rounded backdrop-blur border border-white/5">
|
||||
GEGR. {city.establishedYear}
|
||||
</div>
|
||||
)}
|
||||
<h1 className="text-4xl md:text-6xl font-bold text-white tracking-tight mb-2">{city.name}</h1>
|
||||
<div className="flex items-center gap-4 text-sm text-gray-300">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Icons.Users className="w-4 h-4" /> {city.memberCount} Mitglieder
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<div className="w-2 h-2 rounded-full bg-accentSuccess animate-pulse" /> {city.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{city.mayor && (
|
||||
<div className="text-right">
|
||||
<div className="text-xs text-textMuted uppercase tracking-widest mb-1">Aktueller Anführer</div>
|
||||
<div className="flex items-center gap-2 text-lg font-medium text-amber-100 bg-surfaceHighlight/60 px-4 py-2 rounded-lg backdrop-blur border border-amber-500/30 shadow-[0_0_15px_rgba(245,158,11,0.15)] group hover:border-amber-500/50 transition-colors">
|
||||
<Icons.Crown className="w-5 h-5 text-amber-400 drop-shadow-md" />
|
||||
{city.mayor}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation Tabs */}
|
||||
<div className="flex gap-1 border-b border-border mb-8 overflow-x-auto">
|
||||
<button
|
||||
onClick={() => setActiveTab('overview')}
|
||||
className={`px-6 py-3 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${activeTab === 'overview' ? 'border-accentInfo text-white' : 'border-transparent text-textMuted hover:text-white'}`}
|
||||
>
|
||||
Übersicht & Galerie
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('residents')}
|
||||
className={`px-6 py-3 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${activeTab === 'residents' ? 'border-accentInfo text-white' : 'border-transparent text-textMuted hover:text-white'}`}
|
||||
>
|
||||
Bewohner ({residents.length})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('ventures')}
|
||||
className={`px-6 py-3 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${activeTab === 'ventures' ? 'border-accentInfo text-white' : 'border-transparent text-textMuted hover:text-white'}`}
|
||||
>
|
||||
Lokale Unternehmen ({ventures.length})
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content Area */}
|
||||
<div className="min-h-[400px]">
|
||||
{activeTab === 'overview' && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2 space-y-8">
|
||||
<section>
|
||||
<h3 className="text-xl font-bold mb-4 flex items-center gap-2">
|
||||
<Icons.Map className="w-5 h-5 text-accentInfo" /> Über {city.name}
|
||||
</h3>
|
||||
<p className="text-textMuted leading-relaxed text-lg">
|
||||
{city.description}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{city.gallery && city.gallery.length > 0 && (
|
||||
<section>
|
||||
<h3 className="text-xl font-bold mb-4">Bildarchiv</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{city.gallery.map((url, idx) => (
|
||||
<div key={idx} className="rounded-xl overflow-hidden aspect-video border border-border group relative">
|
||||
<img src={url} alt={`Gallery ${idx}`} className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110" />
|
||||
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{city.cityStats && (
|
||||
<div className="bg-surface border border-border rounded-xl p-6 h-fit">
|
||||
<h4 className="text-sm font-bold uppercase text-textMuted mb-6 flex items-center gap-2">
|
||||
<Icons.Terminal className="w-4 h-4" /> Stadt-Statistiken
|
||||
</h4>
|
||||
<div className="space-y-6">
|
||||
|
||||
{/* Tax Rate */}
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-green-500/10 rounded text-green-400">
|
||||
<Icons.Coins className="w-4 h-4" />
|
||||
</div>
|
||||
<span className="text-sm text-gray-400">Steuersatz</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="font-mono text-lg text-white font-medium">{city.cityStats.taxRate}%</span>
|
||||
<div className="text-[10px] text-textMuted">Pro Transaktion</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-white/5 h-px" />
|
||||
|
||||
{/* Defense */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-blue-500/10 rounded text-blue-400">
|
||||
<Icons.Shield className="w-4 h-4" />
|
||||
</div>
|
||||
<span className="text-sm text-gray-400">Verteidigungswert</span>
|
||||
</div>
|
||||
<span className="font-mono text-white font-medium">{city.cityStats.defenseRating}/10</span>
|
||||
</div>
|
||||
<div className="h-1.5 w-full bg-surfaceHighlight rounded-full overflow-hidden">
|
||||
<div className="h-full bg-blue-500/70" style={{ width: `${(city.cityStats.defenseRating / 10) * 100}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-white/5 h-px" />
|
||||
|
||||
{/* Gov Type */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-purple-500/10 rounded text-purple-400">
|
||||
<Icons.Scroll className="w-4 h-4" />
|
||||
</div>
|
||||
<span className="text-sm text-gray-400">Regierungsform</span>
|
||||
</div>
|
||||
<span className="text-sm text-white font-medium">{city.cityStats.government}</span>
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-white/5 h-px" />
|
||||
|
||||
{/* Biome */}
|
||||
<div className="flex justify-between items-center py-1">
|
||||
<span className="text-sm text-gray-400">Biom</span>
|
||||
<span className="text-sm text-white">{city.cityStats.biome}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center py-1">
|
||||
<span className="text-sm text-gray-400">Spezialität</span>
|
||||
<span className="text-sm text-white">{city.cityStats.specialty}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'residents' && (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
{residents.map(player => (
|
||||
<div
|
||||
key={player.uuid}
|
||||
onClick={() => onSelectPlayer(player.uuid)}
|
||||
className="bg-surface border border-border p-4 rounded-xl flex items-center gap-4 hover:border-accentInfo/50 transition-colors cursor-pointer group hover:shadow-card hover:bg-surfaceHighlight/30"
|
||||
>
|
||||
<div className="w-10 h-10 bg-surfaceHighlight rounded flex items-center justify-center font-bold text-lg text-textMuted group-hover:text-textMain transition-colors">
|
||||
{player.username.charAt(0)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-white group-hover:text-accentInfo transition-colors">{player.username}</div>
|
||||
<div className="text-xs text-textMuted">{player.stats.role}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{residents.length === 0 && <div className="text-textMuted italic">Keine Bewohner öffentlich registriert.</div>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'ventures' && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{ventures.map(project => (
|
||||
<div
|
||||
key={project.id}
|
||||
onClick={() => onSelectProject(project.id)}
|
||||
className="bg-surface border border-border rounded-xl p-5 hover:border-accentInfo/50 transition-all cursor-pointer group hover:shadow-card hover:bg-surfaceHighlight/30"
|
||||
>
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<span className={`text-[10px] uppercase font-bold px-2 py-0.5 rounded ${
|
||||
project.category === 'Story Arc' ? 'bg-orange-500/10 text-orange-400' : 'bg-blue-500/10 text-blue-400'
|
||||
}`}>
|
||||
{project.category}
|
||||
</span>
|
||||
{project.hiring && (
|
||||
<span className="text-[10px] font-bold text-accentSuccess animate-pulse">STELLEN</span>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-white mb-1 group-hover:text-accentInfo transition-colors">{project.title}</h3>
|
||||
<p className="text-sm text-textMuted mb-4 line-clamp-2">{project.description}</p>
|
||||
<div className="flex items-center justify-between pt-4 border-t border-white/5">
|
||||
<span className="text-xs text-textMuted">Geführt von {project.owner}</span>
|
||||
<div className="text-xs font-mono text-white">{project.progress}% Fortschr.</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{ventures.length === 0 && <div className="text-textMuted italic">Keine aktiven Unternehmen in dieser Organisation.</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CityProfile;
|
||||
Reference in New Issue
Block a user