mirror of
https://github.com/ceratic/project_vollidioten_website.git
synced 2026-05-14 00:16:47 +02:00
feat: Add DatabaseManager and LinkPlayer components, implement authentication and linking logic
- Created DatabaseManager component for managing database access via phpMyAdmin. - Developed LinkPlayer component to link Discord accounts with game characters, including user authentication and error handling. - Added mock data files for players, organizations, and projects to handle backend unavailability. - Implemented AuthService for managing user authentication and session checks. - Created DatabaseService to fetch and manage player, organization, and project data with fallback to mock data. - Added HTML page for handling authentication unavailability. - Developed a test script for validating Docker setup and required files.
This commit is contained in:
@@ -1,7 +1,15 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Project, ShopItem } from '../types';
|
||||
import { MOCK_ORGS, MOCK_PLAYERS } from '../constants';
|
||||
import { Icons, ItemIcon } from '../components/IconSet';
|
||||
import { dbService } from '../services/DatabaseService';
|
||||
import { authService } from '../services/AuthService';
|
||||
import { DiscordUser } from '../types';
|
||||
import EditModal from '../components/EditModal';
|
||||
import ShopManagementModal from '../components/ShopManagementModal';
|
||||
import EmployeeManagementModal from '../components/EmployeeManagementModal';
|
||||
import BannerManagementModal from '../components/BannerManagementModal';
|
||||
import GalleryManagementModal from '../components/GalleryManagementModal';
|
||||
import DeleteProjectModal from '../components/DeleteProjectModal';
|
||||
|
||||
interface ProjectProfileProps {
|
||||
project: Project;
|
||||
@@ -10,18 +18,46 @@ interface ProjectProfileProps {
|
||||
onSelectOrg: (id: string) => void;
|
||||
}
|
||||
|
||||
const ProjectProfile: React.FC<ProjectProfileProps> = ({
|
||||
project,
|
||||
const ProjectProfile: React.FC<ProjectProfileProps> = ({
|
||||
project,
|
||||
onBack,
|
||||
onSelectPlayer,
|
||||
onSelectOrg
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState<'overview' | 'shop'>('overview');
|
||||
|
||||
const org = project.associatedOrgId ? MOCK_ORGS.find(o => o.id === project.associatedOrgId) : null;
|
||||
const ownerPlayer = MOCK_PLAYERS.find(p => p.username === project.owner);
|
||||
const [activeTab, setActiveTab] = useState<'overview' | 'shop' | 'manage'>('overview');
|
||||
const [user, setUser] = useState<DiscordUser | null>(null);
|
||||
const [org, setOrg] = useState<any>(null);
|
||||
const [ownerPlayer, setOwnerPlayer] = useState<any>(null);
|
||||
const [isEditDescriptionOpen, setIsEditDescriptionOpen] = useState(false);
|
||||
const [isEditHiringOpen, setIsEditHiringOpen] = useState(false);
|
||||
const [isShopModalOpen, setIsShopModalOpen] = useState(false);
|
||||
const [isEmployeeModalOpen, setIsEmployeeModalOpen] = useState(false);
|
||||
const [isBannerModalOpen, setIsBannerModalOpen] = useState(false);
|
||||
const [isGalleryModalOpen, setIsGalleryModalOpen] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
|
||||
// Subscribe to auth and data updates
|
||||
useEffect(() => {
|
||||
const unsubAuth = authService.subscribe(setUser);
|
||||
return unsubAuth;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Load associated org and owner player
|
||||
if (project.associatedOrgId) {
|
||||
const foundOrg = dbService.getOrg(project.associatedOrgId);
|
||||
setOrg(foundOrg);
|
||||
}
|
||||
|
||||
// Find owner player by username
|
||||
const players = dbService.getPlayers();
|
||||
const owner = players.find(p => p.username === project.owner);
|
||||
setOwnerPlayer(owner);
|
||||
}, [project]);
|
||||
|
||||
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') || [];
|
||||
@@ -105,20 +141,28 @@ const ProjectProfile: React.FC<ProjectProfileProps> = ({
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex gap-8 border-b border-border mb-8 px-2">
|
||||
<button
|
||||
<button
|
||||
onClick={() => setActiveTab('overview')}
|
||||
className={`pb-4 text-sm font-medium transition-colors border-b-2 ${activeTab === 'overview' ? 'border-accentInfo text-white' : 'border-transparent text-textMuted hover:text-white'}`}
|
||||
>
|
||||
Übersicht
|
||||
</button>
|
||||
{hasShop && (
|
||||
<button
|
||||
<button
|
||||
onClick={() => setActiveTab('shop')}
|
||||
className={`pb-4 text-sm font-medium transition-colors border-b-2 ${activeTab === 'shop' ? 'border-accentInfo text-white' : 'border-transparent text-textMuted hover:text-white'}`}
|
||||
>
|
||||
Katalog ({project.shopCatalog?.length})
|
||||
</button>
|
||||
)}
|
||||
{isOwner && (
|
||||
<button
|
||||
onClick={() => setActiveTab('manage')}
|
||||
className={`pb-4 text-sm font-medium transition-colors border-b-2 ${activeTab === 'manage' ? 'border-accentInfo text-white' : 'border-transparent text-textMuted hover:text-white'}`}
|
||||
>
|
||||
Verwalten
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
@@ -156,10 +200,11 @@ const ProjectProfile: React.FC<ProjectProfileProps> = ({
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{project.employees.map((emp, idx) => {
|
||||
const empPlayer = MOCK_PLAYERS.find(p => p.username === emp);
|
||||
const players = dbService.getPlayers();
|
||||
const empPlayer = players.find(p => p.username === emp);
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
<div
|
||||
key={idx}
|
||||
onClick={() => empPlayer && onSelectPlayer(empPlayer.uuid)}
|
||||
className={`flex items-center gap-3 bg-surface border border-border p-3 rounded-lg ${empPlayer ? 'cursor-pointer hover:border-accentInfo/50 hover:bg-surfaceHighlight/30 transition-all group' : ''}`}
|
||||
>
|
||||
@@ -247,6 +292,117 @@ const ProjectProfile: React.FC<ProjectProfileProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'manage' && isOwner && (
|
||||
<div className="space-y-8">
|
||||
<div className="bg-surface/50 border border-border rounded-xl p-6">
|
||||
<h3 className="text-lg font-bold text-white mb-6 flex items-center gap-2">
|
||||
<Icons.Edit className="w-5 h-5 text-accentInfo" />
|
||||
Unternehmen verwalten
|
||||
</h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Edit Description */}
|
||||
<div className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
||||
<h4 className="text-sm font-semibold text-textMain mb-3">Manifest bearbeiten</h4>
|
||||
<p className="text-xs text-textMuted mb-4">
|
||||
Ändern Sie die Beschreibung und das Leitbild Ihres Unternehmens.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsEditDescriptionOpen(true)}
|
||||
className="w-full bg-accentInfo hover:bg-accentInfo/90 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
||||
>
|
||||
Beschreibung bearbeiten
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Toggle Hiring */}
|
||||
<div className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
||||
<h4 className="text-sm font-semibold text-textMain mb-3">Stellenanzeigen</h4>
|
||||
<p className="text-xs text-textMuted mb-4">
|
||||
Aktuell: {project.hiring ? 'Stellen sind ausgeschrieben' : 'Keine offenen Stellen'}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsEditHiringOpen(true)}
|
||||
className="w-full bg-accentInfo hover:bg-accentInfo/90 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
||||
>
|
||||
{project.hiring ? 'Stellenanzeigen beenden' : 'Stellen ausschreiben'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Shop Management */}
|
||||
<div className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
||||
<h4 className="text-sm font-semibold text-textMain mb-3">Shop verwalten</h4>
|
||||
<p className="text-xs text-textMuted mb-4">
|
||||
Fügen Sie Produkte, Dienstleistungen und Preise hinzu.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsShopModalOpen(true)}
|
||||
className="w-full bg-purple-500 hover:bg-purple-600 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
||||
>
|
||||
Shop bearbeiten
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Employee Management */}
|
||||
<div className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
||||
<h4 className="text-sm font-semibold text-textMain mb-3">Mitarbeiter verwalten</h4>
|
||||
<p className="text-xs text-textMuted mb-4">
|
||||
Fügen Sie Mitarbeiter hinzu oder entfernen Sie sie.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsEmployeeModalOpen(true)}
|
||||
className="w-full bg-green-500 hover:bg-green-600 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
||||
>
|
||||
Mitarbeiter verwalten
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Banner Management */}
|
||||
<div className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
||||
<h4 className="text-sm font-semibold text-textMain mb-3">Banner ändern</h4>
|
||||
<p className="text-xs text-textMuted mb-4">
|
||||
Ändern Sie das Titelbild Ihres Unternehmens.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsBannerModalOpen(true)}
|
||||
className="w-full bg-orange-500 hover:bg-orange-600 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
||||
>
|
||||
Banner bearbeiten
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Gallery Management */}
|
||||
<div className="bg-surfaceHighlight/30 border border-border rounded-lg p-4">
|
||||
<h4 className="text-sm font-semibold text-textMain mb-3">Galerie verwalten</h4>
|
||||
<p className="text-xs text-textMuted mb-4">
|
||||
Fügen Sie Bilder zu Ihrem Portfolio hinzu.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsGalleryModalOpen(true)}
|
||||
className="w-full bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
||||
>
|
||||
Galerie bearbeiten
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Danger Zone */}
|
||||
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-4">
|
||||
<h4 className="text-sm font-semibold text-red-400 mb-3">Gefahrenzone</h4>
|
||||
<p className="text-xs text-red-300 mb-4">
|
||||
Unwiderrufliche Aktionen für dieses Unternehmen.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsDeleteModalOpen(true)}
|
||||
className="w-full bg-red-500 hover:bg-red-600 text-white text-sm font-medium py-2 px-4 rounded transition-colors"
|
||||
>
|
||||
Unternehmen löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'shop' && project.shopCatalog && (
|
||||
<div className="space-y-12">
|
||||
|
||||
@@ -342,9 +498,141 @@ const ProjectProfile: React.FC<ProjectProfileProps> = ({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Edit Modals */}
|
||||
<EditModal
|
||||
isOpen={isEditDescriptionOpen}
|
||||
title="Manifest bearbeiten"
|
||||
initialValue={project.description}
|
||||
multiline={true}
|
||||
markdown={true}
|
||||
onClose={() => setIsEditDescriptionOpen(false)}
|
||||
onSave={async (newDescription) => {
|
||||
try {
|
||||
const response = await fetch(`https://vollidioten.ceraticsoft.de/api/projects/${project.id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ description: newDescription })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Update local data
|
||||
dbService.updateProject(project.id, { description: newDescription });
|
||||
console.log('Description updated successfully');
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
alert(`Fehler beim Aktualisieren: ${errorData.error || 'Unbekannter Fehler'}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error updating description:', err);
|
||||
alert('Netzwerkfehler beim Aktualisieren der Beschreibung');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<EditModal
|
||||
isOpen={isEditHiringOpen}
|
||||
title={project.hiring ? 'Stellenanzeigen beenden' : 'Stellen ausschreiben'}
|
||||
initialValue={project.hiring ? 'Ja, Stellen sind derzeit ausgeschrieben.' : 'Nein, derzeit keine offenen Stellen.'}
|
||||
multiline={false}
|
||||
onClose={() => setIsEditHiringOpen(false)}
|
||||
onSave={async (value) => {
|
||||
const newHiringStatus = !project.hiring;
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://vollidioten.ceraticsoft.de/api/projects/${project.id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ hiring: newHiringStatus })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Update local data
|
||||
dbService.updateProject(project.id, { hiring: newHiringStatus });
|
||||
console.log('Hiring status updated successfully:', newHiringStatus);
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
alert(`Fehler beim Aktualisieren: ${errorData.error || 'Unbekannter Fehler'}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error updating hiring status:', err);
|
||||
alert('Netzwerkfehler beim Aktualisieren der Stellenanzeigen');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Management Modals */}
|
||||
<ShopManagementModal
|
||||
isOpen={isShopModalOpen}
|
||||
onClose={() => setIsShopModalOpen(false)}
|
||||
projectId={project.id}
|
||||
onUpdate={() => {
|
||||
// Refresh project data
|
||||
console.log('Shop updated, refreshing project data...');
|
||||
// The dbService should automatically update via subscription
|
||||
}}
|
||||
/>
|
||||
|
||||
<EmployeeManagementModal
|
||||
isOpen={isEmployeeModalOpen}
|
||||
onClose={() => setIsEmployeeModalOpen(false)}
|
||||
projectId={project.id}
|
||||
onUpdate={() => {
|
||||
// Refresh project data
|
||||
console.log('Employees updated, refreshing project data...');
|
||||
}}
|
||||
/>
|
||||
|
||||
<BannerManagementModal
|
||||
isOpen={isBannerModalOpen}
|
||||
onClose={() => setIsBannerModalOpen(false)}
|
||||
projectId={project.id}
|
||||
currentBannerUrl={project.bannerUrl || ''}
|
||||
onUpdate={() => {
|
||||
// Refresh project data
|
||||
console.log('Banner updated, refreshing project data...');
|
||||
}}
|
||||
/>
|
||||
|
||||
<GalleryManagementModal
|
||||
isOpen={isGalleryModalOpen}
|
||||
onClose={() => setIsGalleryModalOpen(false)}
|
||||
projectId={project.id}
|
||||
onUpdate={() => {
|
||||
// Refresh project data
|
||||
console.log('Gallery updated, refreshing project data...');
|
||||
}}
|
||||
/>
|
||||
|
||||
<DeleteProjectModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
onClose={() => setIsDeleteModalOpen(false)}
|
||||
projectId={project.id}
|
||||
projectTitle={project.title}
|
||||
onDelete={async () => {
|
||||
try {
|
||||
const response = await fetch(`https://vollidioten.ceraticsoft.de/api/projects/${project.id}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('Projekt erfolgreich gelöscht!');
|
||||
onBack(); // Navigate back to projects list
|
||||
} else {
|
||||
alert('Fehler beim Löschen des Projekts');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error deleting project:', err);
|
||||
alert('Netzwerkfehler beim Löschen');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectProfile;
|
||||
export default ProjectProfile;
|
||||
|
||||
Reference in New Issue
Block a user