fixed minor issues,
This commit is contained in:
parent
b53c186342
commit
0886757f89
4
data/editable_text.json
Normal file → Executable file
4
data/editable_text.json
Normal file → Executable file
@ -27,8 +27,8 @@
|
||||
"label": "Ich enthalte mich"
|
||||
},
|
||||
{
|
||||
"id": "nichts",
|
||||
"label": "Wild"
|
||||
"id": "123",
|
||||
"label": "123"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
0
data/jwt_keys.json
Normal file → Executable file
0
data/jwt_keys.json
Normal file → Executable file
1366
data/member_credentials.json
Normal file → Executable file
1366
data/member_credentials.json
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
0
data/responses.json
Normal file → Executable file
0
data/responses.json
Normal file → Executable file
0
data/settings.json
Normal file → Executable file
0
data/settings.json
Normal file → Executable file
0
data/used_tokens.json
Normal file → Executable file
0
data/used_tokens.json
Normal file → Executable file
@ -24,7 +24,7 @@ export default function PublicResultsPage() {
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.texts && Array.isArray(data.texts)) {
|
||||
const voteOptionsEntry = data.texts.find((text: any) => text.id === 'vote-options');
|
||||
const voteOptionsEntry = data.texts.find((text: { id: string }) => text.id === 'vote-options');
|
||||
if (voteOptionsEntry && Array.isArray(voteOptionsEntry.content)) {
|
||||
setVoteOptions(voteOptionsEntry.content);
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ export default function AdminPage() {
|
||||
setMemberAuthEnabled(data.settings.memberAuthEnabled);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
} catch {
|
||||
// Silently fail, user will need to enter password
|
||||
}
|
||||
};
|
||||
@ -190,7 +190,7 @@ export default function AdminPage() {
|
||||
setEditableTexts(data.texts);
|
||||
|
||||
// Find and set vote options
|
||||
const voteOptionsEntry = data.texts.find((text: any) => text.id === 'vote-options');
|
||||
const voteOptionsEntry = data.texts.find((text: { id: string }) => text.id === 'vote-options');
|
||||
if (voteOptionsEntry && Array.isArray(voteOptionsEntry.content)) {
|
||||
setVoteOptions(voteOptionsEntry.content);
|
||||
}
|
||||
@ -336,45 +336,6 @@ export default function AdminPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// Update a vote option
|
||||
const handleUpdateVoteOption = async (optionId: string, newLabel: string) => {
|
||||
if (!newLabel.trim()) {
|
||||
setError('Das Label darf nicht leer sein');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// Update in local state
|
||||
const updatedOptions = voteOptions.map(option =>
|
||||
option.id === optionId ? { ...option, label: newLabel } : option
|
||||
);
|
||||
setVoteOptions(updatedOptions);
|
||||
|
||||
// Save to server
|
||||
const response = await fetch('/api/editable-text', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: 'vote-options',
|
||||
content: updatedOptions
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Speichern der Abstimmungsoptionen');
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const copyToClipboard = () => {
|
||||
if (generatedLink) {
|
||||
navigator.clipboard.writeText(generatedLink);
|
||||
@ -469,7 +430,7 @@ export default function AdminPage() {
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || 'Fehler beim Generieren der Tokens');
|
||||
} catch (jsonError) {
|
||||
} catch {
|
||||
// If not JSON, use status text
|
||||
throw new Error(`Fehler beim Generieren der Tokens: ${response.statusText}`);
|
||||
}
|
||||
@ -668,7 +629,7 @@ export default function AdminPage() {
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label htmlFor="newOptionId" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Option ID (z.B. "yes", "no")
|
||||
Option ID (z.B. "yes", "no")
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
|
@ -5,7 +5,7 @@ import path from 'path';
|
||||
|
||||
// Path to the editable text file
|
||||
const TEXT_FILE = path.join(process.cwd(), 'data', 'editable_text.json');
|
||||
|
||||
export type EditableText = { id: string; content: string };
|
||||
// Ensure the data directory exists
|
||||
function ensureDataDirectory() {
|
||||
const dataDir = path.join(process.cwd(), 'data');
|
||||
@ -61,7 +61,8 @@ function getEditableTexts() {
|
||||
}
|
||||
|
||||
// Save editable texts
|
||||
function saveEditableTexts(texts: any[]) {
|
||||
|
||||
function saveEditableTexts(texts: EditableText[]) {
|
||||
ensureDataDirectory();
|
||||
fs.writeFileSync(TEXT_FILE, JSON.stringify(texts, null, 2));
|
||||
}
|
||||
@ -107,7 +108,9 @@ export async function POST(request: NextRequest) {
|
||||
const texts = getEditableTexts();
|
||||
|
||||
// Find and update the text with the given ID
|
||||
const textIndex = texts.findIndex((text: any) => text.id === id);
|
||||
const textIndex = texts.findIndex((text: EditableText
|
||||
|
||||
) => text.id === id);
|
||||
|
||||
if (textIndex === -1) {
|
||||
// Text not found, add new
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { checkAdminAuth, generateRandomToken } from '@/lib/auth';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
// Get admin password from environment variable or use default for development
|
||||
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'schafwaschener-segelverein-admin';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
|
@ -2,7 +2,7 @@ import { checkAdminAuth } from '@/lib/auth';
|
||||
import { addMember, deleteMember, getMemberCredentials, updateMember } from '@/lib/server-auth';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
export async function GET() {
|
||||
try {
|
||||
// Check for admin auth
|
||||
const isAuthenticated = await checkAdminAuth();
|
||||
@ -18,7 +18,7 @@ export async function GET(request: NextRequest) {
|
||||
const members = getMemberCredentials();
|
||||
|
||||
// Remove password from response
|
||||
const sanitizedMembers = members.map(({ password, ...rest }) => rest);
|
||||
const sanitizedMembers = members.map((v) => ({ ...v, password: undefined }));
|
||||
|
||||
return NextResponse.json({ members: sanitizedMembers });
|
||||
} catch (error) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getSettings } from '@/lib/server-auth';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
export async function GET() {
|
||||
try {
|
||||
// Get settings
|
||||
const settings = getSettings();
|
||||
|
@ -4,10 +4,8 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { password } = body;
|
||||
// Check for admin auth
|
||||
const isAuthenticated = await checkAdminAuth(password);
|
||||
const isAuthenticated = await checkAdminAuth();
|
||||
if (!isAuthenticated) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Unauthorized' },
|
||||
|
@ -29,7 +29,7 @@ export default function LoginPage() {
|
||||
if (response.status === 403) {
|
||||
setIsEnabled(false);
|
||||
}
|
||||
} catch (err) {
|
||||
} catch {
|
||||
// Silently fail, assume enabled
|
||||
}
|
||||
};
|
||||
|
@ -1,12 +1,25 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, Suspense } from 'react';
|
||||
import { useSearchParams, useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { EditableText } from '@/components/EditableText';
|
||||
import { VoteOption, VoteOptionConfig } from '@/lib/survey';
|
||||
|
||||
|
||||
export default function VotePage() {
|
||||
return (
|
||||
<Suspense fallback={<LoadingComponent />}>
|
||||
<VotePageContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
function LoadingComponent() {
|
||||
return <div>Lädt...</div>;
|
||||
}
|
||||
|
||||
function VotePageContent() {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const token = searchParams.get('token');
|
||||
@ -17,7 +30,6 @@ export default function VotePage() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||
|
||||
// Fetch vote options
|
||||
useEffect(() => {
|
||||
const fetchVoteOptions = async () => {
|
||||
@ -26,7 +38,7 @@ export default function VotePage() {
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.texts && Array.isArray(data.texts)) {
|
||||
const voteOptionsEntry = data.texts.find((text: any) => text.id === 'vote-options');
|
||||
const voteOptionsEntry = data.texts.find((text: { id: string }) => text.id === 'vote-options');
|
||||
if (voteOptionsEntry && Array.isArray(voteOptionsEntry.content)) {
|
||||
setVoteOptions(voteOptionsEntry.content);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, forwardRef } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useEffect, useState } from 'react';
|
||||
// Import ReactQuill dynamically to avoid SSR issues
|
||||
const ReactQuill = dynamic(() => import('react-quill-new'), {
|
||||
ssr: false,
|
||||
@ -33,7 +33,7 @@ const QuillEditor = ({ value, onChange, placeholder }: QuillEditorProps) => {
|
||||
toolbar: [
|
||||
[{ 'header': [1, 2, 3, false] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
||||
['link'],
|
||||
['clean']
|
||||
]
|
||||
|
@ -18,8 +18,8 @@ export interface MemberCredential {
|
||||
export async function generateRandomToken(memberNumber?: string): Promise<string> {
|
||||
// This function is used in API routes
|
||||
const tokenId = uuidv4();
|
||||
|
||||
const payload: any = { tokenId };
|
||||
type Payload = { tokenId: string; memberNumber?: string };
|
||||
const payload: Payload = { tokenId };
|
||||
|
||||
// If memberNumber is provided, include it in the token
|
||||
if (memberNumber) {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { SignJWT, jwtVerify } from 'jose';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
import { jwtVerify } from 'jose';
|
||||
import path from 'path';
|
||||
import { MemberCredential, comparePassword, hashPassword } from './auth';
|
||||
|
||||
// Keys for JWT signing and verification
|
||||
|
@ -47,7 +47,7 @@ export function getVoteOptions(): VoteOptionConfig[] {
|
||||
const data = fs.readFileSync(TEXT_FILE, 'utf-8');
|
||||
const texts = JSON.parse(data);
|
||||
|
||||
const voteOptionsEntry = texts.find((text: any) => text.id === 'vote-options');
|
||||
const voteOptionsEntry = texts.find((text: { id: string }) => text.id === 'vote-options');
|
||||
|
||||
if (voteOptionsEntry && Array.isArray(voteOptionsEntry.content)) {
|
||||
return voteOptionsEntry.content;
|
||||
|
@ -1,8 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
// This middleware doesn't do anything special yet
|
||||
// It's just a placeholder for future authentication middleware
|
||||
return NextResponse.next();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user