mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
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:
38
App.tsx
38
App.tsx
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { Routes, Route, useNavigate, useLocation } from 'react-router-dom';
|
||||
import Layout from './components/Layout';
|
||||
import Dashboard from './pages/Dashboard';
|
||||
import Players from './pages/Players';
|
||||
import PlayerProfile from './pages/PlayerProfile';
|
||||
import SetupGuide from './pages/SetupGuide';
|
||||
import Projects from './pages/Projects';
|
||||
@@ -109,42 +110,7 @@ function App() {
|
||||
<Route path="/datapack" element={<DatapackGenerator />} />
|
||||
|
||||
{/* Player Routes */}
|
||||
<Route path="/players" element={
|
||||
<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" element={<Players onSelectPlayer={navigateToPlayer} />} />
|
||||
<Route path="/players/:id" element={<PlayerProfile />} />
|
||||
|
||||
{/* City Routes */}
|
||||
|
||||
191
constants.ts
191
constants.ts
@@ -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' }
|
||||
]
|
||||
},
|
||||
];
|
||||
@@ -53,6 +53,17 @@ const Cities: React.FC<CitiesProps> = ({ onSelectCity }) => {
|
||||
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);
|
||||
};
|
||||
|
||||
const loadCities = () => {
|
||||
// Get all organizations from database
|
||||
const allOrgs = dbService.getOrgs();
|
||||
@@ -63,7 +74,7 @@ const Cities: React.FC<CitiesProps> = ({ onSelectCity }) => {
|
||||
// Count citizens (players with this organizationId)
|
||||
const allPlayers = dbService.getPlayers();
|
||||
const citizenCount = allPlayers.filter(player =>
|
||||
player.stats.organizationId === city.id
|
||||
player.organizationId === city.id
|
||||
).length;
|
||||
|
||||
// Count businesses/projects in this city
|
||||
@@ -87,11 +98,10 @@ const Cities: React.FC<CitiesProps> = ({ onSelectCity }) => {
|
||||
|
||||
setCities(cityOrgs);
|
||||
setCitiesWithStats(citiesStats);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
// Initial load
|
||||
loadCities();
|
||||
// Load fresh data on mount
|
||||
loadData();
|
||||
|
||||
// Subscribe to updates
|
||||
const unsub = dbService.subscribe(loadCities);
|
||||
|
||||
@@ -270,8 +270,8 @@ const CityProfile: React.FC = () => {
|
||||
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 className="w-10 h-10 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-medium text-white group-hover:text-accentInfo transition-colors">{player.username}</div>
|
||||
|
||||
@@ -48,19 +48,26 @@ const Dashboard: React.FC = () => {
|
||||
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 unsubscribe = dbService.subscribe(() => {
|
||||
setPlayers(dbService.getPlayers());
|
||||
setProjects(dbService.getProjects());
|
||||
setOrgs(dbService.getOrgs());
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
// Initial data load
|
||||
setPlayers(dbService.getPlayers());
|
||||
setProjects(dbService.getProjects());
|
||||
setOrgs(dbService.getOrgs());
|
||||
setLoading(false);
|
||||
// Load fresh data on mount
|
||||
loadData();
|
||||
|
||||
return unsubscribe;
|
||||
}, []);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { MOCK_ORGS } from '../constants';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Organization } from '../types';
|
||||
import { Icons } from '../components/IconSet';
|
||||
import { dbService } from '../services/DatabaseService';
|
||||
|
||||
const OrgCard = ({ org, onClick }: { org: Organization; onClick: () => void }) => (
|
||||
<div
|
||||
@@ -43,8 +43,35 @@ const OrgCard = ({ org, onClick }: { org: Organization; onClick: () => void }) =
|
||||
|
||||
const Organizations: React.FC<{ onSelectOrg: (id: string) => void }> = ({ onSelectOrg }) => {
|
||||
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
|
||||
);
|
||||
|
||||
@@ -83,16 +110,26 @@ const Organizations: React.FC<{ onSelectOrg: (id: string) => void }> = ({ onSele
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||
{filteredOrgs.map(org => (
|
||||
<OrgCard key={org.id} org={org} onClick={() => onSelectOrg(org.id)} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{filteredOrgs.length === 0 && (
|
||||
<div className="text-center py-20 text-textMuted">
|
||||
<p>Keine Organisationen gefunden.</p>
|
||||
{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-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>
|
||||
);
|
||||
|
||||
80
pages/Players.tsx
Normal file
80
pages/Players.tsx
Normal 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;
|
||||
@@ -138,14 +138,24 @@ const Projects: React.FC<ProjectsProps> = ({ onSelectProject }) => {
|
||||
|
||||
// Subscribe to auth and data updates
|
||||
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 unsubDb = dbService.subscribe(() => {
|
||||
setProjects(dbService.getProjects());
|
||||
});
|
||||
|
||||
// Initial data load
|
||||
setProjects(dbService.getProjects());
|
||||
setLoading(false);
|
||||
// Load fresh data on mount
|
||||
loadData();
|
||||
|
||||
return () => {
|
||||
unsubAuth();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -1,20 +1,18 @@
|
||||
import { MOCK_PLAYERS, MOCK_ORGS, MOCK_PROJECTS } from '../constants';
|
||||
import { Player, Organization, Project } from '../types';
|
||||
|
||||
const API_URL = 'https://vollidioten.ceraticsoft.de/api';
|
||||
const MOCK_DATA_HEADER = 'X-Mock-Data';
|
||||
|
||||
class DatabaseService {
|
||||
private players: Player[] = MOCK_PLAYERS;
|
||||
private orgs: Organization[] = MOCK_ORGS;
|
||||
private projects: Project[] = MOCK_PROJECTS;
|
||||
private players: Player[] = [];
|
||||
private orgs: Organization[] = [];
|
||||
private projects: Project[] = [];
|
||||
private listeners: Function[] = [];
|
||||
|
||||
constructor() {
|
||||
this.fetchAll();
|
||||
}
|
||||
|
||||
// Try to fetch real data from backend
|
||||
// Fetch data from backend - always try to load fresh data
|
||||
async fetchAll() {
|
||||
try {
|
||||
console.log("Fetching data from API...");
|
||||
@@ -24,11 +22,6 @@ class DatabaseService {
|
||||
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) {
|
||||
const playersData = await pRes.json();
|
||||
const orgsData = await oRes.json();
|
||||
@@ -41,19 +34,14 @@ class DatabaseService {
|
||||
this.projects = projectsData;
|
||||
this.notify();
|
||||
|
||||
if (isMockData) {
|
||||
console.warn("Backend unavailable - using mock data from nginx fallback");
|
||||
} else {
|
||||
console.log("✅ Connected to Backend Database - using real data");
|
||||
}
|
||||
console.log("✅ Connected to Backend Database - using real data");
|
||||
} else {
|
||||
console.warn(`API returned errors: players=${pRes.status}, orgs=${oRes.status}, projects=${prRes.status}`);
|
||||
console.warn("Using built-in mock data");
|
||||
// Data is already set to mock data in constructor
|
||||
throw new Error("Backend unavailable");
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Backend not available, using built-in Mock Data.", e);
|
||||
// Fallback is already set in constructor
|
||||
console.warn("Backend not available, data not loaded.", e);
|
||||
throw e; // Re-throw so components can handle the error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user