Refactor CityProfile and PlayerProfile components for improved data fetching and error handling; add NPC management modals for banner, gallery, and logo with enhanced user experience and error feedback.

This commit is contained in:
Lars Behrends
2025-12-30 13:56:00 +01:00
parent 5eb2eca110
commit c6ad8a92ec
14 changed files with 2539 additions and 102 deletions

View File

@@ -21,6 +21,19 @@ const PlayerProfile: React.FC = () => {
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState<'story' | 'stats' | 'projects'>('story');
const handleNavigateToOrg = (orgId: string) => {
const org = dbService.getOrg(orgId);
if (org?.type === 'City') {
navigate(`/cities/${orgId}`);
} else {
navigate(`/organizations/${orgId}`);
}
};
const handleNavigateToProject = (projectId: string) => {
navigate(`/projects/${projectId}`);
};
// Is this the logged-in user's profile?
const isOwner = currentUser?.linkedPlayerUuid === player?.uuid;
const playerOrg = player ? dbService.getOrg(player.organizationId || '') : null;
@@ -36,17 +49,37 @@ const PlayerProfile: React.FC = () => {
return;
}
// Load player data
const playerData = dbService.getPlayer(id);
if (playerData) {
setPlayer(playerData);
} else {
navigate('/players');
return;
}
// Load player data - try to fetch from API first, fallback to cache
const loadPlayerData = async () => {
try {
const playerData = await dbService.fetchPlayer(id);
if (playerData) {
setPlayer(playerData);
} else {
// Fallback to cache if API fails
const cachedPlayer = dbService.getPlayer(id);
if (cachedPlayer) {
setPlayer(cachedPlayer);
} else {
navigate('/players');
return;
}
}
} catch (error) {
console.error('Error loading player data:', error);
// Fallback to cache
const cachedPlayer = dbService.getPlayer(id);
if (cachedPlayer) {
setPlayer(cachedPlayer);
} else {
navigate('/players');
return;
}
}
setLoading(false);
};
console.log(playerData);
setLoading(false);
loadPlayerData();
}, [id, navigate]);
useEffect(() => {
@@ -252,27 +285,38 @@ const PlayerProfile: React.FC = () => {
<div className="bg-surface border border-border rounded-xl p-4 shadow-card">
<h3 className="text-xs font-bold uppercase tracking-wider text-textMuted mb-3">Zugehörigkeit</h3>
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded flex items-center justify-center text-lg font-bold border border-white/5 ${
playerOrg
? (playerOrg.type === 'City' ? 'bg-blue-500/10 text-blue-400' :
playerOrg.type === 'Guild' ? 'bg-amber-500/10 text-amber-400' :
'bg-purple-500/10 text-purple-400')
: 'bg-surfaceHighlight text-textMuted'
}`}>
{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.minecraftStats?.role || 'Unbekannt'}</div>
<div className="text-xs text-textMuted">
{playerOrg ? (
<span className="group-hover:text-accentInfo transition-colors">{playerOrg.name}</span>
) : (
'Freiberufler / Keine Zugehörigkeit'
)}
{playerOrg ? (
<div
onClick={() => handleNavigateToOrg(playerOrg.id)}
className="flex items-center gap-3 cursor-pointer hover:bg-surfaceHighlight/50 -m-1 p-1 rounded transition-colors group"
>
<div className={`w-10 h-10 rounded flex items-center justify-center text-lg font-bold border border-white/5 ${
playerOrg.type === 'City' ? 'bg-blue-500/10 text-blue-400' :
playerOrg.type === 'Guild' ? 'bg-amber-500/10 text-amber-400' :
'bg-purple-500/10 text-purple-400'
}`}>
{playerOrg.name.charAt(0)}
</div>
<div>
<div className="text-sm font-medium text-textMain group-hover:text-accentInfo transition-colors">
{playerOrg.name}
</div>
<div className="text-xs text-textMuted">
{player.minecraftStats?.role || 'Unbekannt'} Klick zum Anzeigen
</div>
</div>
</div>
</div>
) : (
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded flex items-center justify-center text-lg font-bold border border-white/5 bg-surfaceHighlight text-textMuted">
<Icons.Map className="w-5 h-5 opacity-50" />
</div>
<div>
<div className="text-sm font-medium text-textMain">Freiberufler</div>
<div className="text-xs text-textMuted">Keine Zugehörigkeit</div>
</div>
</div>
)}
</div>
</div>
@@ -437,9 +481,15 @@ const PlayerProfile: React.FC = () => {
<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
key={project.id}
onClick={() => handleNavigateToProject(project.id)}
className="bg-surfaceHighlight/30 border border-border rounded-lg p-4 hover:border-accentInfo/50 transition-all cursor-pointer group"
>
<div className="flex justify-between items-start mb-2">
<h4 className="font-medium text-textMain">{project.title}</h4>
<h4 className="font-medium text-textMain group-hover:text-accentInfo transition-colors">
{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' :
@@ -468,6 +518,9 @@ const PlayerProfile: React.FC = () => {
</span>
</div>
</div>
<div className="text-xs text-accentInfo mt-2 opacity-0 group-hover:opacity-100 transition-opacity">
Klick zum Anzeigen
</div>
</div>
))}
</div>