mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
routing
This commit is contained in:
@@ -1,23 +1,17 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Organization, Project, Player } from '../types';
|
||||
import { Icons } from '../components/IconSet';
|
||||
import { dbService } from '../services/DatabaseService';
|
||||
|
||||
interface CityProfileProps {
|
||||
city: Organization;
|
||||
onBack: () => void;
|
||||
backLabel?: string;
|
||||
onSelectPlayer: (id: string) => void;
|
||||
onSelectProject: (id: string) => void;
|
||||
}
|
||||
const CityProfile: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [city, setCity] = useState<Organization | null>(null);
|
||||
const backLabel = 'Zurück zu Städte';
|
||||
|
||||
const CityProfile: React.FC<CityProfileProps> = ({
|
||||
city,
|
||||
onBack,
|
||||
backLabel = 'Zurück',
|
||||
onSelectPlayer,
|
||||
onSelectProject
|
||||
}) => {
|
||||
const onSelectPlayer = (playerId: string) => navigate(`/players/${playerId}`);
|
||||
const onSelectProject = (projectId: string) => navigate(`/projects/${projectId}`);
|
||||
const [activeTab, setActiveTab] = useState<'overview' | 'residents' | 'ventures'>('overview');
|
||||
const [residents, setResidents] = useState<Player[]>([]);
|
||||
const [ventures, setVentures] = useState<Project[]>([]);
|
||||
@@ -25,6 +19,26 @@ const CityProfile: React.FC<CityProfileProps> = ({
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) {
|
||||
navigate('/cities');
|
||||
return;
|
||||
}
|
||||
|
||||
// Load city data
|
||||
const cityData = dbService.getOrg(id);
|
||||
if (cityData) {
|
||||
setCity(cityData);
|
||||
} else {
|
||||
navigate('/cities');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}, [id, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!city) return;
|
||||
|
||||
const loadCityData = () => {
|
||||
// Load residents (players in this city)
|
||||
const allPlayers = dbService.getPlayers();
|
||||
@@ -46,8 +60,6 @@ const CityProfile: React.FC<CityProfileProps> = ({
|
||||
...city.cityStats
|
||||
};
|
||||
setCityStats(stats);
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
// Initial load
|
||||
@@ -56,11 +68,21 @@ const CityProfile: React.FC<CityProfileProps> = ({
|
||||
// Subscribe to updates
|
||||
const unsub = dbService.subscribe(loadCityData);
|
||||
return unsub;
|
||||
}, [city.id]);
|
||||
}, [city]);
|
||||
|
||||
if (loading || !city) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto animate-in slide-in-from-right-4 duration-300">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
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">
|
||||
<button onClick={() => navigate('/cities')} 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>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Player } from '../types';
|
||||
import { dbService } from '../services/DatabaseService';
|
||||
import { authService } from '../services/AuthService';
|
||||
@@ -6,24 +7,22 @@ import InventoryGrid from '../components/InventoryGrid';
|
||||
import EditModal from '../components/EditModal';
|
||||
import { Icons } from '../components/IconSet';
|
||||
|
||||
interface PlayerProfileProps {
|
||||
player: Player;
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
const PlayerProfile: React.FC<PlayerProfileProps> = ({ player: initialPlayer, onBack }) => {
|
||||
const [player, setPlayer] = useState(initialPlayer);
|
||||
const PlayerProfile: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [player, setPlayer] = useState<Player | null>(null);
|
||||
const [currentUser, setCurrentUser] = useState(authService.getUser());
|
||||
|
||||
|
||||
// Edit State
|
||||
const [isEditStoryOpen, setIsEditStoryOpen] = useState(false);
|
||||
const [isEditTagsOpen, setIsEditTagsOpen] = useState(false);
|
||||
const [isEditOrgOpen, setIsEditOrgOpen] = useState(false);
|
||||
const [ownedProjects, setOwnedProjects] = useState<any[]>([]);
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// Is this the logged-in user's profile?
|
||||
const isOwner = currentUser?.linkedPlayerUuid === player.uuid;
|
||||
const playerOrg = dbService.getOrg(player.stats.organizationId || '');
|
||||
const isOwner = currentUser?.linkedPlayerUuid === player?.uuid;
|
||||
const playerOrg = player ? dbService.getOrg(player.stats.organizationId || '') : null;
|
||||
|
||||
// Check if player is already linked to anyone in our mock/real DB logic
|
||||
// Since the Player object doesn't expose 'discordId' publicly in types yet (it's hidden in DB),
|
||||
@@ -31,9 +30,25 @@ const PlayerProfile: React.FC<PlayerProfileProps> = ({ player: initialPlayer, on
|
||||
const canClaim = !!currentUser && !currentUser.linkedPlayerUuid && !isOwner;
|
||||
|
||||
useEffect(() => {
|
||||
// Refresh player data from "DB" to ensure we have latest local edits
|
||||
const freshData = dbService.getPlayer(player.uuid);
|
||||
if (freshData) setPlayer(freshData);
|
||||
if (!id) {
|
||||
navigate('/players');
|
||||
return;
|
||||
}
|
||||
|
||||
// Load player data
|
||||
const playerData = dbService.getPlayer(id);
|
||||
if (playerData) {
|
||||
setPlayer(playerData);
|
||||
} else {
|
||||
navigate('/players');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}, [id, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!player) return;
|
||||
|
||||
// Load owned projects
|
||||
const allProjects = dbService.getProjects();
|
||||
@@ -43,7 +58,17 @@ const PlayerProfile: React.FC<PlayerProfileProps> = ({ player: initialPlayer, on
|
||||
// Subscribe to auth to show/hide edit buttons
|
||||
const unsub = authService.subscribe(u => setCurrentUser(u));
|
||||
return unsub;
|
||||
}, [player.uuid, player.username]);
|
||||
}, [player]);
|
||||
|
||||
if (loading || !player) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto animate-in slide-in-from-right-4 duration-300">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
const handleSaveStory = (newStory: string) => {
|
||||
// Update local DB
|
||||
@@ -113,7 +138,7 @@ const PlayerProfile: React.FC<PlayerProfileProps> = ({ player: initialPlayer, on
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto animate-in slide-in-from-right-4 duration-300">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<button onClick={onBack} className="flex items-center gap-2 text-sm text-textMuted hover:text-textMain transition-colors">
|
||||
<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
|
||||
</button>
|
||||
{canClaim && (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Project, ShopItem } from '../types';
|
||||
import { Icons, ItemIcon } from '../components/IconSet';
|
||||
import { dbService } from '../services/DatabaseService';
|
||||
@@ -11,19 +12,11 @@ import BannerManagementModal from '../components/BannerManagementModal';
|
||||
import GalleryManagementModal from '../components/GalleryManagementModal';
|
||||
import DeleteProjectModal from '../components/DeleteProjectModal';
|
||||
|
||||
interface ProjectProfileProps {
|
||||
project: Project;
|
||||
onBack: () => void;
|
||||
onSelectPlayer: (id: string) => void;
|
||||
onSelectOrg: (id: string) => void;
|
||||
}
|
||||
|
||||
const ProjectProfile: React.FC<ProjectProfileProps> = ({
|
||||
project,
|
||||
onBack,
|
||||
onSelectPlayer,
|
||||
onSelectOrg
|
||||
}) => {
|
||||
const ProjectProfile: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [project, setProject] = useState<Project | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [activeTab, setActiveTab] = useState<'overview' | 'shop' | 'manage'>('overview');
|
||||
const [user, setUser] = useState<DiscordUser | null>(null);
|
||||
const [org, setOrg] = useState<any>(null);
|
||||
@@ -43,6 +36,26 @@ const ProjectProfile: React.FC<ProjectProfileProps> = ({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) {
|
||||
navigate('/projects');
|
||||
return;
|
||||
}
|
||||
|
||||
// Load project data
|
||||
const projectData = dbService.getProject(id);
|
||||
if (projectData) {
|
||||
setProject(projectData);
|
||||
} else {
|
||||
navigate('/projects');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}, [id, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!project) return;
|
||||
|
||||
// Load associated org and owner player
|
||||
if (project.associatedOrgId) {
|
||||
const foundOrg = dbService.getOrg(project.associatedOrgId);
|
||||
@@ -55,16 +68,37 @@ const ProjectProfile: React.FC<ProjectProfileProps> = ({
|
||||
setOwnerPlayer(owner);
|
||||
}, [project]);
|
||||
|
||||
const hasShop = project.shopCatalog && project.shopCatalog.length > 0;
|
||||
const isOwner = user?.linkedPlayerUuid && ownerPlayer && dbService.getPlayer(user.linkedPlayerUuid)?.username === project.owner;
|
||||
const onSelectPlayer = (playerId: string) => navigate(`/players/${playerId}`);
|
||||
const onSelectOrg = (orgId: string) => {
|
||||
const org = dbService.getOrgs().find(o => o.id === orgId);
|
||||
if (org?.type === 'City') {
|
||||
navigate(`/cities/${orgId}`);
|
||||
} else {
|
||||
navigate(`/organizations/${orgId}`);
|
||||
}
|
||||
};
|
||||
const onBack = () => navigate('/projects');
|
||||
|
||||
const hasShop = project?.shopCatalog && project.shopCatalog.length > 0;
|
||||
const isOwner = user?.linkedPlayerUuid && ownerPlayer && dbService.getPlayer(user.linkedPlayerUuid)?.username === project?.owner;
|
||||
|
||||
// Group shop items
|
||||
const services = project.shopCatalog?.filter(i => i.type === 'service') || [];
|
||||
const products = project.shopCatalog?.filter(i => i.type !== 'service') || [];
|
||||
const services = project?.shopCatalog?.filter(i => i.type === 'service') || [];
|
||||
const products = project?.shopCatalog?.filter(i => i.type !== 'service') || [];
|
||||
|
||||
if (loading || !project) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto animate-in slide-in-from-right-4 duration-300">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
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">
|
||||
<button onClick={() => navigate('/projects')} 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> Zurück zum Verzeichnis
|
||||
</button>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user