feat: implement Players component with data fetching and loading state; remove mock data and enhance error handling in database service

This commit is contained in:
Lars Behrends
2025-12-30 15:58:07 +01:00
parent c6ad8a92ec
commit ea2b803534
12 changed files with 184 additions and 341 deletions

38
App.tsx
View File

@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { Routes, Route, useNavigate, useLocation } from 'react-router-dom'; import { Routes, Route, useNavigate, useLocation } from 'react-router-dom';
import Layout from './components/Layout'; import Layout from './components/Layout';
import Dashboard from './pages/Dashboard'; import Dashboard from './pages/Dashboard';
import Players from './pages/Players';
import PlayerProfile from './pages/PlayerProfile'; import PlayerProfile from './pages/PlayerProfile';
import SetupGuide from './pages/SetupGuide'; import SetupGuide from './pages/SetupGuide';
import Projects from './pages/Projects'; import Projects from './pages/Projects';
@@ -109,42 +110,7 @@ function App() {
<Route path="/datapack" element={<DatapackGenerator />} /> <Route path="/datapack" element={<DatapackGenerator />} />
{/* Player Routes */} {/* Player Routes */}
<Route path="/players" element={ <Route path="/players" element={<Players onSelectPlayer={navigateToPlayer} />} />
<div className="animate-in fade-in">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold">Bürgerverzeichnis</h2>
<div className="relative">
<input
type="text"
placeholder="Filtern nach Tag oder Name..."
className="bg-surfaceHighlight border border-border rounded-lg pl-10 pr-4 py-2 text-sm text-textMain focus:border-accentInfo focus:outline-none w-64 transition-colors"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
{players.map(player => (
<div
key={player.uuid}
onClick={() => navigateToPlayer(player.uuid)}
className="group bg-surface border border-border p-4 rounded-xl cursor-pointer hover:border-accentInfo/50 transition-all duration-200 hover:shadow-card"
>
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-md flex items-center justify-center font-bold text-lg text-textMuted group-hover:text-textMain transition-colors">
<img src={"https://minotar.net/armor/bust/"+player.username+"/500.png"}></img>
</div>
<div>
<div className="font-semibold text-textMain group-hover:text-accentInfo transition-colors">{player.username}</div>
<div className="text-xs text-textMuted mt-1 flex gap-2">
{player.tags.slice(0, 2).map(t => <span key={t}>{t}</span>)}
</div>
</div>
</div>
</div>
))}
</div>
</div>
} />
<Route path="/players/:id" element={<PlayerProfile />} /> <Route path="/players/:id" element={<PlayerProfile />} />
{/* City Routes */} {/* City Routes */}

View File

@@ -1,191 +0,0 @@
import { Player, Organization, Project } from './types';
export const MOCK_PLAYERS: Player[] = [
{
uuid: '80301bff-74df-4579-bcfc-082ac8d26b5b',
username: 'kaiwastoshort',
isOnline: true,
tags: ['#Bürger', '#Händler'],
stats: {
playtimeHours: 482,
level: 45,
role: 'Bürger',
organizationId: 'org-3' // Moved to Old Haven
},
inventory: [
{ id: 'minecraft:diamond_pickaxe', name: 'Behutsamkeit Hacke', count: 1, type: 'tool', rarity: 'epic', nbtSummary: 'Eff V, Rep.' },
{ id: 'minecraft:stone_bricks', name: 'Polierter Andesit', count: 64, type: 'block' },
{ id: 'minecraft:stone_bricks', name: 'Polierter Andesit', count: 32, type: 'block' },
null, null,
{ id: 'minecraft:cooked_beef', name: 'Steak', count: 12, type: 'consumable' },
{ id: 'minecraft:compass', name: 'Stadtkarte', count: 1, type: 'misc', nbtSummary: 'Ziel: Spawn' },
null, null
],
storyMarkdown: `
# Der Bauplan von V
> "Stein erinnert sich an das, was Eisen vergisst."
**V** kam während der *Großen Expansion* ins Tal. Ursprünglich ein einfacher Maurer, stieg er nach dem Entwurf des **Aquädukt-Systems** schnell in den Rängen auf.
### Bekannte Verbindungen
* Die Händlergilde
* Nördliche Entdecker
### Aktuelles Ziel
Restaurierung des verfallenen Wachturms am östlichen Grat.
`
},
{
uuid: '8984c0b5-d912-4462-b189-c864fba4a1af',
username: 'DrKButz',
isOnline: false,
tags: ['#Bauunternehmer'],
stats: {
playtimeHours: 120,
level: 12,
role: 'Unternehmer',
organizationId: 'org-4'
},
inventory: [
{ id: 'minecraft:redstone', name: 'Redstone Staub', count: 64, type: 'misc' },
{ id: 'minecraft:comparator', name: 'Komparator', count: 16, type: 'misc' },
null,
{ id: 'minecraft:book', name: 'Forschungsnotizen', count: 1, type: 'misc', nbtSummary: 'Seite 12: Logikgatter' },
null, null, null, null, null
],
storyMarkdown: `
# Forschungslogbuch:
Spezialisiert auf automatisierte Logistik. Gerüchten zufolge hat er eine Maschine gebaut, die Items schneller sortieren kann als jeder Mensch.
* Sucht aktuell: Quarz
* Verkauft: Auto-Schmelzöfen
`
},
{
uuid: 'b3b84518-03a2-4b48-8551-448c3f7a7d77',
username: 'ceratic_test',
isOnline: true,
tags: ['#Bürger', '#Händler'],
stats: {
playtimeHours: 50,
level: 5,
role: 'Bürger',
organizationId: 'org-3'
},
inventory: [],
storyMarkdown: 'Einfach nur eine einfache Bäckerin, die gutes Brot liebt.'
},
{
uuid: '3c10008b-1eb2-4d89-8b8a-6d03c821eb09',
username: 'merkursun',
isOnline: true,
tags: ['#Bürger'],
stats: {
playtimeHours: 50,
level: 5,
role: 'Bürger',
organizationId: 'org-4'
},
inventory: [],
storyMarkdown: 'Einfach nur eine einfache Bäckerin, die gutes Brot liebt.'
}
];
export const MOCK_ORGS: Organization[] = [
/* {
id: 'org-2',
name: 'Butz Building GmbH',
type: 'Company',
description: 'Baugewerbe.',
memberCount: 1,
status: 'active'
},*/
{
id: 'org-3',
name: 'Provisorium Null',
type: 'City',
description: 'Die erste Siedlung, eingebettet zwischen den Zwillingsgipfeln. Bekannt für das geschäftige Marktviertel und die alten Steinmauern.',
memberCount: 6,
status: 'active',
mayor: '',
establishedYear: 'Day 0',
bannerUrl: 'images/screenshots/2025-12-28_01.11.10.png',
gallery: [
'images/screenshots/2025-12-28_01.12.07.png', // Market
'images/screenshots/2025-12-28_01.12.17.png', // Castle
'images/screenshots/2025-12-28_01.12.24.png' // Walls
],
cityStats: {
taxRate: 3.5,
biome: 'Ebene / Gebirge',
defenseRating: 8,
government: 'Feudalmonarchie',
specialty: 'Handel & Stein'
}
},
{
id: 'org-4',
name: 'Sakura',
type: 'City',
description: 'Eine dunkle, biolumineszente Hafenstadt in den tiefen Höhlen. Heimat von Schmugglern und Händlern seltener Mineralien.',
memberCount: 2,
status: 'active',
mayor: 'Kampfzwerk',
establishedYear: 'Ära 2, Jahr 10',
bannerUrl: 'images/screenshots/2025-12-28_01.11.32.png',
gallery: [
'images/screenshots/2025-12-28_01.11.38.png',
'images/screenshots/2025-12-28_01.11.44.png'
],
cityStats: {
taxRate: 15.0,
biome: 'Deep Dark / Lush Caves',
defenseRating: 4,
government: 'Syndikat',
specialty: 'Schmuggelware & Erze'
}
},
];
export const MOCK_PROJECTS: Project[] = [
{
id: 'ven-1',
title: 'DrkButz Architektur & Mauerwerk',
description: 'Führendes Architekturbüro, spezialisiert auf gotische Strukturen und Verteidigungsmauern. Wir entwerfen dein Vermächtnis.',
category: 'Enterprise',
status: 'active',
progress: 85, // Reputation
owner: 'DrKButz',
employees: [],
hiring: false,
foundedDate: 'Zyklus 12',
associatedOrgId: 'org-4', // Old Haven
bannerUrl: 'images/screenshots/2025-12-28_01.11.49.png',
shopCatalog: [
{ id: 's2', name: 'Geländevermessung', description: '1 Stunde Beratung zur Geländevorbereitung.', price: 20, currency: 'Gold', stock: 10, type: 'service' },
{ id: 's3', name: 'Mauerbau', description: 'Wir bauen deine Außenmauern. Preis pro Chunk.', price: 100, currency: 'Gold', stock: 5, type: 'service', materialsRequired: 'Kunde stellt Steinziegel' },
{ id: 's4', name: 'Terraforming', description: 'Einebnung und Landschaftsbau.', price: 75, currency: 'Gold', stock: 2, type: 'service' }
]
},
{
id: 'ven-5',
title: 'Tinas Tavern',
description: 'Täglich frisches Brot zum Spawn-Markt geliefert. Lieferant der königlichen Garde.',
category: 'Enterprise',
status: 'active',
progress: 98,
owner: 'kaiwastoshort',
employees: [],
hiring: false,
associatedOrgId: 'org-3',
bannerUrl: 'images/screenshots/tinas.png',
shopCatalog: [
{ id: 'f1', name: 'Redstone Block', description: '', price: 2, currency: 'Diamonds', stock: 45, type: 'item' },
{ id: 'f2', name: 'Honey', description: '', price: 5, currency: 'Diamonds', stock: 12, type: 'item' },
{ id: 'f3', name: 'Slime', description: '', price: 10, currency: 'Diamonds', stock: 4, type: 'item' },
{ id: 'f4', name: 'Bier', description: '', price: 3, currency: 'Diamonds', stock: 20, type: 'item' }
]
},
];

View File

@@ -53,6 +53,17 @@ const Cities: React.FC<CitiesProps> = ({ onSelectCity }) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
const loadData = async () => {
setLoading(true);
try {
await dbService.fetchAll();
} catch (error) {
console.warn('Failed to fetch fresh data:', error);
// Continue with cached data
}
setLoading(false);
};
const loadCities = () => { const loadCities = () => {
// Get all organizations from database // Get all organizations from database
const allOrgs = dbService.getOrgs(); const allOrgs = dbService.getOrgs();
@@ -63,7 +74,7 @@ const Cities: React.FC<CitiesProps> = ({ onSelectCity }) => {
// Count citizens (players with this organizationId) // Count citizens (players with this organizationId)
const allPlayers = dbService.getPlayers(); const allPlayers = dbService.getPlayers();
const citizenCount = allPlayers.filter(player => const citizenCount = allPlayers.filter(player =>
player.stats.organizationId === city.id player.organizationId === city.id
).length; ).length;
// Count businesses/projects in this city // Count businesses/projects in this city
@@ -87,11 +98,10 @@ const Cities: React.FC<CitiesProps> = ({ onSelectCity }) => {
setCities(cityOrgs); setCities(cityOrgs);
setCitiesWithStats(citiesStats); setCitiesWithStats(citiesStats);
setLoading(false);
}; };
// Initial load // Load fresh data on mount
loadCities(); loadData();
// Subscribe to updates // Subscribe to updates
const unsub = dbService.subscribe(loadCities); const unsub = dbService.subscribe(loadCities);

View File

@@ -270,8 +270,8 @@ const CityProfile: React.FC = () => {
onClick={() => onSelectPlayer(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" 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"> <div className="w-10 h-10 flex items-center justify-center font-bold text-lg text-textMuted group-hover:text-textMain transition-colors">
{player.username.charAt(0)} <img src={"https://minotar.net/armor/bust/"+player.username+"/500.png"}></img>
</div> </div>
<div> <div>
<div className="font-medium text-white group-hover:text-accentInfo transition-colors">{player.username}</div> <div className="font-medium text-white group-hover:text-accentInfo transition-colors">{player.username}</div>

View File

@@ -48,19 +48,26 @@ const Dashboard: React.FC = () => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
const loadData = async () => {
setLoading(true);
try {
await dbService.fetchAll();
} catch (error) {
console.warn('Failed to fetch fresh data:', error);
// Continue with cached data
}
setLoading(false);
};
// Subscribe to database updates // Subscribe to database updates
const unsubscribe = dbService.subscribe(() => { const unsubscribe = dbService.subscribe(() => {
setPlayers(dbService.getPlayers()); setPlayers(dbService.getPlayers());
setProjects(dbService.getProjects()); setProjects(dbService.getProjects());
setOrgs(dbService.getOrgs()); setOrgs(dbService.getOrgs());
setLoading(false);
}); });
// Initial data load // Load fresh data on mount
setPlayers(dbService.getPlayers()); loadData();
setProjects(dbService.getProjects());
setOrgs(dbService.getOrgs());
setLoading(false);
return unsubscribe; return unsubscribe;
}, []); }, []);

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { MOCK_ORGS } from '../constants';
import { Organization } from '../types'; import { Organization } from '../types';
import { Icons } from '../components/IconSet'; import { Icons } from '../components/IconSet';
import { dbService } from '../services/DatabaseService';
const OrgCard = ({ org, onClick }: { org: Organization; onClick: () => void }) => ( const OrgCard = ({ org, onClick }: { org: Organization; onClick: () => void }) => (
<div <div
@@ -43,8 +43,35 @@ const OrgCard = ({ org, onClick }: { org: Organization; onClick: () => void }) =
const Organizations: React.FC<{ onSelectOrg: (id: string) => void }> = ({ onSelectOrg }) => { const Organizations: React.FC<{ onSelectOrg: (id: string) => void }> = ({ onSelectOrg }) => {
const [filter, setFilter] = useState<'all' | Organization['type']>('all'); const [filter, setFilter] = useState<'all' | Organization['type']>('all');
const [orgs, setOrgs] = useState<Organization[]>([]);
const [loading, setLoading] = useState(true);
const filteredOrgs = MOCK_ORGS.filter(org => useEffect(() => {
const loadData = async () => {
setLoading(true);
try {
await dbService.fetchAll();
} catch (error) {
console.warn('Failed to fetch fresh data:', error);
// Continue with cached data
}
setLoading(false);
};
const loadOrgs = () => {
const allOrgs = dbService.getOrgs();
setOrgs(allOrgs);
};
// Load fresh data on mount
loadData();
// Subscribe to updates
const unsub = dbService.subscribe(loadOrgs);
return unsub;
}, []);
const filteredOrgs = orgs.filter(org =>
filter === 'all' ? true : org.type === filter filter === 'all' ? true : org.type === filter
); );
@@ -73,8 +100,8 @@ const Organizations: React.FC<{ onSelectOrg: (id: string) => void }> = ({ onSele
key={tab.id} key={tab.id}
onClick={() => setFilter(tab.id as any)} onClick={() => setFilter(tab.id as any)}
className={`px-4 py-2 text-sm font-medium rounded-t-lg transition-colors relative top-[1px] ${ className={`px-4 py-2 text-sm font-medium rounded-t-lg transition-colors relative top-[1px] ${
filter === tab.id filter === tab.id
? 'text-textMain border-b-2 border-accentInfo bg-surfaceHighlight/20' ? 'text-textMain border-b-2 border-accentInfo bg-surfaceHighlight/20'
: 'text-textMuted hover:text-textMain hover:bg-white/5' : 'text-textMuted hover:text-textMain hover:bg-white/5'
}`} }`}
> >
@@ -83,19 +110,29 @@ const Organizations: React.FC<{ onSelectOrg: (id: string) => void }> = ({ onSele
))} ))}
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6"> {loading ? (
{filteredOrgs.map(org => ( <div className="flex justify-center py-20">
<OrgCard key={org.id} org={org} onClick={() => onSelectOrg(org.id)} /> <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-accentInfo"></div>
))}
</div>
{filteredOrgs.length === 0 && (
<div className="text-center py-20 text-textMuted">
<p>Keine Organisationen gefunden.</p>
</div> </div>
) : (
<>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
{filteredOrgs.map(org => (
<div key={org.id}>
<OrgCard org={org} onClick={() => onSelectOrg(org.id)} />
</div>
))}
</div>
{filteredOrgs.length === 0 && (
<div className="text-center py-20 text-textMuted">
<p>Keine Organisationen gefunden.</p>
</div>
)}
</>
)} )}
</div> </div>
); );
}; };
export default Organizations; export default Organizations;

80
pages/Players.tsx Normal file
View File

@@ -0,0 +1,80 @@
import React, { useState, useEffect } from 'react';
import { Player } from '../types';
import { dbService } from '../services/DatabaseService';
interface PlayersProps {
onSelectPlayer: (id: string) => void;
}
const Players: React.FC<PlayersProps> = ({ onSelectPlayer }) => {
const [players, setPlayers] = useState<Player[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadData = async () => {
setLoading(true);
try {
await dbService.fetchAll();
} catch (error) {
console.warn('Failed to fetch fresh data:', error);
// Continue with cached data
}
setLoading(false);
};
// Subscribe to database updates
const unsubDb = dbService.subscribe(() => {
setPlayers(dbService.getPlayers());
});
// Load fresh data on mount
loadData();
return unsubDb;
}, []);
return (
<div className="animate-in fade-in">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold">Bürgerverzeichnis</h2>
<div className="relative">
<input
type="text"
placeholder="Filtern nach Tag oder Name..."
className="bg-surfaceHighlight border border-border rounded-lg pl-10 pr-4 py-2 text-sm text-textMain focus:border-accentInfo focus:outline-none w-64 transition-colors"
/>
</div>
</div>
{loading ? (
<div className="flex justify-center py-20">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-accentInfo"></div>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
{players.map(player => (
<div
key={player.uuid}
onClick={() => onSelectPlayer(player.uuid)}
className="group bg-surface border border-border p-4 rounded-xl cursor-pointer hover:border-accentInfo/50 transition-all duration-200 hover:shadow-card"
>
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-md flex items-center justify-center font-bold text-lg text-textMuted group-hover:text-textMain transition-colors">
<img src={"https://minotar.net/armor/bust/"+player.username+"/500.png"}></img>
</div>
<div>
<div className="font-semibold text-textMain group-hover:text-accentInfo transition-colors">{player.username}</div>
<div className="text-xs text-textMuted mt-1 flex gap-2">
{player.tags.slice(0, 2).map(t => <span key={t}>{t}</span>)}
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
);
};
export default Players;

View File

@@ -138,14 +138,24 @@ const Projects: React.FC<ProjectsProps> = ({ onSelectProject }) => {
// Subscribe to auth and data updates // Subscribe to auth and data updates
useEffect(() => { useEffect(() => {
const loadData = async () => {
setLoading(true);
try {
await dbService.fetchAll();
} catch (error) {
console.warn('Failed to fetch fresh data:', error);
// Continue with cached data
}
setLoading(false);
};
const unsubAuth = authService.subscribe(setUser); const unsubAuth = authService.subscribe(setUser);
const unsubDb = dbService.subscribe(() => { const unsubDb = dbService.subscribe(() => {
setProjects(dbService.getProjects()); setProjects(dbService.getProjects());
}); });
// Initial data load // Load fresh data on mount
setProjects(dbService.getProjects()); loadData();
setLoading(false);
return () => { return () => {
unsubAuth(); unsubAuth();

View File

@@ -1,14 +0,0 @@
[
{
"id": 1,
"name": "Mock Organization",
"description": "This is a mock organization that appears when the backend is unavailable.",
"leader": "DrKButz",
"members": ["8984c0b5-d912-4462-b189-c864fba4a1af"],
"gallery": [],
"cityStats": {
"influence": 50,
"reputation": 75
}
}
]

View File

@@ -1,37 +0,0 @@
[
{
"uuid": "8984c0b5-d912-4462-b189-c864fba4a1af",
"name": "DrKButz",
"discordId": "mock_discord_id",
"storyMarkdown": "# Mock Player Story\n\nThis is a mock player story that appears when the backend is unavailable.",
"tags": ["admin", "developer"],
"stats": {
"health": 100,
"armor": 50,
"money": 10000
},
"inventory": [
{"id": "weapon_pistol", "name": "Pistol", "quantity": 1},
{"id": "item_medkit", "name": "Medkit", "quantity": 3}
],
"isOnline": false,
"lastSeen": "2025-01-01T12:00:00Z"
},
{
"uuid": "123e4567-e89b-12d3-a456-426614174000",
"name": "TestPlayer",
"discordId": null,
"storyMarkdown": "# Test Player\n\nThis is a test player for mock data purposes.",
"tags": ["player"],
"stats": {
"health": 80,
"armor": 30,
"money": 5000
},
"inventory": [
{"id": "item_food", "name": "Food", "quantity": 5}
],
"isOnline": true,
"lastSeen": "2025-01-01T10:00:00Z"
}
]

View File

@@ -1,13 +0,0 @@
[
{
"id": 1,
"name": "Mock Project",
"description": "This is a mock project that appears when the backend is unavailable.",
"leader": "DrKButz",
"employees": ["8984c0b5-d912-4462-b189-c864fba4a1af"],
"shopCatalog": [],
"gallery": [],
"hiring": false,
"status": "active"
}
]

View File

@@ -1,20 +1,18 @@
import { MOCK_PLAYERS, MOCK_ORGS, MOCK_PROJECTS } from '../constants';
import { Player, Organization, Project } from '../types'; import { Player, Organization, Project } from '../types';
const API_URL = 'https://vollidioten.ceraticsoft.de/api'; const API_URL = 'https://vollidioten.ceraticsoft.de/api';
const MOCK_DATA_HEADER = 'X-Mock-Data';
class DatabaseService { class DatabaseService {
private players: Player[] = MOCK_PLAYERS; private players: Player[] = [];
private orgs: Organization[] = MOCK_ORGS; private orgs: Organization[] = [];
private projects: Project[] = MOCK_PROJECTS; private projects: Project[] = [];
private listeners: Function[] = []; private listeners: Function[] = [];
constructor() { constructor() {
this.fetchAll(); this.fetchAll();
} }
// Try to fetch real data from backend // Fetch data from backend - always try to load fresh data
async fetchAll() { async fetchAll() {
try { try {
console.log("Fetching data from API..."); console.log("Fetching data from API...");
@@ -24,11 +22,6 @@ class DatabaseService {
fetch(`${API_URL}/projects`) fetch(`${API_URL}/projects`)
]); ]);
// Check if we're getting mock data from nginx fallback
const isMockData = pRes.headers.get(MOCK_DATA_HEADER) === 'true' ||
oRes.headers.get(MOCK_DATA_HEADER) === 'true' ||
prRes.headers.get(MOCK_DATA_HEADER) === 'true';
if (pRes.ok && oRes.ok && prRes.ok) { if (pRes.ok && oRes.ok && prRes.ok) {
const playersData = await pRes.json(); const playersData = await pRes.json();
const orgsData = await oRes.json(); const orgsData = await oRes.json();
@@ -41,19 +34,14 @@ class DatabaseService {
this.projects = projectsData; this.projects = projectsData;
this.notify(); this.notify();
if (isMockData) { console.log("✅ Connected to Backend Database - using real data");
console.warn("Backend unavailable - using mock data from nginx fallback");
} else {
console.log("✅ Connected to Backend Database - using real data");
}
} else { } else {
console.warn(`API returned errors: players=${pRes.status}, orgs=${oRes.status}, projects=${prRes.status}`); console.warn(`API returned errors: players=${pRes.status}, orgs=${oRes.status}, projects=${prRes.status}`);
console.warn("Using built-in mock data"); throw new Error("Backend unavailable");
// Data is already set to mock data in constructor
} }
} catch (e) { } catch (e) {
console.warn("Backend not available, using built-in Mock Data.", e); console.warn("Backend not available, data not loaded.", e);
// Fallback is already set in constructor throw e; // Re-throw so components can handle the error
} }
} }