260 lines
7.6 KiB
TypeScript
260 lines
7.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { VoteOption } from '@/lib/survey';
|
|
import {
|
|
LoginForm,
|
|
TextEditor,
|
|
TextEditorManager,
|
|
StatsButton,
|
|
StatsDisplay,
|
|
VoteOptionsManager,
|
|
MemberAuthManager,
|
|
TokenGenerator,
|
|
ResetVotes
|
|
} from '@/components/admin';
|
|
|
|
// Define types based on the data structure
|
|
interface Stats {
|
|
total: number;
|
|
[key: string]: number; // Allow dynamic keys for vote options
|
|
}
|
|
|
|
interface Comment {
|
|
vote: VoteOption;
|
|
comment: string;
|
|
timestamp: string;
|
|
}
|
|
|
|
interface EditableText {
|
|
id: string;
|
|
content: string;
|
|
}
|
|
|
|
export default function AdminPage() {
|
|
const [showStats, setShowStats] = useState(false);
|
|
const [stats, setStats] = useState<Stats | null>(null);
|
|
const [comments, setComments] = useState<Comment[]>([]);
|
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
const [showEditor, setShowEditor] = useState(false);
|
|
const [editableTexts, setEditableTexts] = useState<EditableText[]>([
|
|
{ id: 'welcome-text', content: '<p>Herzlich willkommen bei der Online-Abstimmungsplattform des SCHAFWASCHENER SEGELVEREIN CHIEMSEE E.V. RIMSTING.</p><p>Diese Plattform ermöglicht es Mitgliedern, sicher und bequem über Vereinsangelegenheiten abzustimmen.</p>' },
|
|
{ id: 'current-vote-text', content: '<p>Derzeit läuft eine Abstimmung zur Änderung der Vereinssatzung.</p><p>Bitte nutzen Sie den Ihnen zugesandten Link, um an der Abstimmung teilzunehmen.</p><p>Bei Fragen wenden Sie sich bitte an den Vorstand.</p>' },
|
|
{ id: 'vote-question', content: '<p>Stimmen Sie der vorgeschlagenen Änderung der Vereinssatzung zu?</p>' }
|
|
]);
|
|
const [voteOptions, setVoteOptions] = useState<{ id: string; label: string }[]>([
|
|
{ id: 'yes', label: 'Ja, ich stimme zu' },
|
|
{ id: 'no', label: 'Nein, ich stimme nicht zu' },
|
|
{ id: 'abstain', label: 'Ich enthalte mich' }
|
|
]);
|
|
const [selectedTextId, setSelectedTextId] = useState<string | null>(null);
|
|
const [editorContent, setEditorContent] = useState('');
|
|
const [memberAuthEnabled, setMemberAuthEnabled] = useState(false);
|
|
|
|
// Check if already authenticated and load settings on component mount
|
|
useEffect(() => {
|
|
const checkAuthAndSettings = async () => {
|
|
try {
|
|
// Check authentication
|
|
const response = await fetch('/api/generate-token', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({}), // Empty body to check if cookie auth works
|
|
});
|
|
|
|
if (response.ok) {
|
|
setIsAuthenticated(true);
|
|
|
|
// Get current settings
|
|
const settingsResponse = await fetch('/api/settings');
|
|
if (settingsResponse.ok) {
|
|
const data = await settingsResponse.json();
|
|
setMemberAuthEnabled(data.settings.memberAuthEnabled);
|
|
}
|
|
}
|
|
} catch {
|
|
// Silently fail, user will need to enter password
|
|
}
|
|
};
|
|
|
|
checkAuthAndSettings();
|
|
}, []);
|
|
|
|
const handleEditText = (textId: string) => {
|
|
const textToEdit = editableTexts.find(text => text.id === textId);
|
|
if (textToEdit) {
|
|
setSelectedTextId(textId);
|
|
setEditorContent(textToEdit.content);
|
|
setShowEditor(true);
|
|
}
|
|
};
|
|
|
|
// Load editable texts and vote options on component mount
|
|
useEffect(() => {
|
|
const loadEditableTexts = async () => {
|
|
try {
|
|
const response = await fetch('/api/editable-text');
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
if (data.texts && Array.isArray(data.texts)) {
|
|
setEditableTexts(data.texts);
|
|
|
|
// Find and set vote options
|
|
const voteOptionsEntry = data.texts.find((text: { id: string }) => text.id === 'vote-options');
|
|
if (voteOptionsEntry && Array.isArray(voteOptionsEntry.content)) {
|
|
setVoteOptions(voteOptionsEntry.content);
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('Error loading editable texts:', err);
|
|
}
|
|
};
|
|
|
|
if (isAuthenticated) {
|
|
loadEditableTexts();
|
|
}
|
|
}, [isAuthenticated]);
|
|
|
|
const handleSaveText = async (content: string) => {
|
|
if (selectedTextId) {
|
|
try {
|
|
// Update local state
|
|
setEditableTexts(prev =>
|
|
prev.map(text =>
|
|
text.id === selectedTextId
|
|
? { ...text, content }
|
|
: text
|
|
)
|
|
);
|
|
|
|
// Save to server
|
|
const response = await fetch('/api/editable-text', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
id: selectedTextId,
|
|
content
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to save text');
|
|
}
|
|
|
|
setShowEditor(false);
|
|
setSelectedTextId(null);
|
|
} catch (err) {
|
|
console.error('Error saving text:', err);
|
|
// Show error message to user if needed
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleStatsLoaded = (loadedStats: Stats, loadedComments: Comment[]) => {
|
|
setStats(loadedStats);
|
|
setComments(loadedComments);
|
|
setShowStats(true);
|
|
};
|
|
|
|
const handleVoteOptionsChange = (newOptions: { id: string; label: string }[]) => {
|
|
setVoteOptions(newOptions);
|
|
};
|
|
|
|
const handleMemberAuthChange = (enabled: boolean) => {
|
|
setMemberAuthEnabled(enabled);
|
|
};
|
|
|
|
const handleResetVotes = () => {
|
|
// If stats are currently shown, refresh them
|
|
if (showStats) {
|
|
// Refresh stats
|
|
const fetchStats = async () => {
|
|
try {
|
|
const response = await fetch('/api/stats', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({}),
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (response.ok) {
|
|
setStats(data.stats);
|
|
setComments(data.comments || []);
|
|
}
|
|
} catch (err) {
|
|
console.error('Error refreshing stats:', err);
|
|
}
|
|
};
|
|
|
|
fetchStats();
|
|
}
|
|
};
|
|
|
|
// Login form if not authenticated
|
|
if (!isAuthenticated) {
|
|
return <LoginForm onLoginSuccess={() => setIsAuthenticated(true)} />;
|
|
}
|
|
|
|
// WYSIWYG editor view
|
|
if (showEditor && selectedTextId) {
|
|
const textToEdit = editableTexts.find(text => text.id === selectedTextId);
|
|
if (textToEdit) {
|
|
return (
|
|
<TextEditor
|
|
textId={selectedTextId}
|
|
initialContent={editorContent}
|
|
onSave={handleSaveText}
|
|
onCancel={() => setShowEditor(false)}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="container mx-auto px-4 py-8">
|
|
<div className="mb-8">
|
|
<h1 className="text-2xl font-bold text-[#0057a6] mb-4">ADMIN-BEREICH</h1>
|
|
|
|
{!showStats ? (
|
|
<div className="ssvc-main-content">
|
|
<StatsButton onStatsLoaded={handleStatsLoaded} />
|
|
|
|
<TextEditorManager
|
|
editableTexts={editableTexts}
|
|
onEditText={handleEditText}
|
|
/>
|
|
|
|
<VoteOptionsManager
|
|
voteOptions={voteOptions}
|
|
onVoteOptionsChange={handleVoteOptionsChange}
|
|
/>
|
|
|
|
<MemberAuthManager
|
|
memberAuthEnabled={memberAuthEnabled}
|
|
onMemberAuthChange={handleMemberAuthChange}
|
|
/>
|
|
|
|
<TokenGenerator />
|
|
|
|
<ResetVotes onReset={handleResetVotes} />
|
|
</div>
|
|
) : (
|
|
<StatsDisplay
|
|
stats={stats!}
|
|
comments={comments}
|
|
voteOptions={voteOptions}
|
|
onBack={() => setShowStats(false)}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|