add custom amount option
This commit is contained in:
parent
1b3847b98d
commit
e7693c4be1
@ -2,16 +2,16 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { getServerSession } from 'next-auth/next';
|
||||
import { authOptions } from '../auth/[...nextauth]'; // Pfad zu deiner authOptions anpassen
|
||||
import prisma from '../../../lib/prisma'; // Pfad zu deinem Prisma Client Singleton anpassen
|
||||
import { Decimal } from '@prisma/client/runtime/library';
|
||||
import prisma from "@/lib/prisma"; // Für korrekte Decimal-Handhabung
|
||||
|
||||
// Definiere die erwartete Struktur des Request-Body
|
||||
interface IncreaseBalanceRequestBody {
|
||||
amount: number; // Der Betrag, um den erhöht werden soll (z.B. 0.10, 0.50, 1.00)
|
||||
amount: number; // Kann jetzt ein beliebiger positiver Betrag oder einer der vordefinierten sein
|
||||
isCustom?: boolean; // Optional: Flag, um zu signalisieren, dass es ein individueller Betrag ist
|
||||
}
|
||||
|
||||
// Definiere die erlaubten Beträge und ihre zugehörigen Transaktionstypen
|
||||
const ALLOWED_AMOUNTS: Record<number, string> = {
|
||||
// Vordefinierte Beträge bleiben optional bestehen
|
||||
const ALLOWED_PREDEFINED_AMOUNTS: Record<number, string> = {
|
||||
0.10: "deposit_0.10",
|
||||
0.20: "deposit_0.20",
|
||||
0.50: "deposit_0.50",
|
||||
@ -19,16 +19,27 @@ const ALLOWED_AMOUNTS: Record<number, string> = {
|
||||
2.00: "deposit_2.00",
|
||||
};
|
||||
|
||||
interface IncreaseBalanceApiResponse {
|
||||
message: string;
|
||||
newBalance: string;
|
||||
transactionId: string;
|
||||
}
|
||||
|
||||
interface ErrorResponse {
|
||||
message: string;
|
||||
allowedAmounts?: number[]; // Für den Fall, dass nur vordefinierte gemeint waren
|
||||
}
|
||||
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
res: NextApiResponse<IncreaseBalanceApiResponse | ErrorResponse>
|
||||
) {
|
||||
if (req.method !== 'POST') {
|
||||
res.setHeader('Allow', ['POST']);
|
||||
return res.status(405).json({ message: `Method ${req.method} Not Allowed` });
|
||||
}
|
||||
|
||||
// Benutzer-Session abrufen, um sicherzustellen, dass der Benutzer angemeldet ist
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
|
||||
if (!session || !session.user || !session.user.id) {
|
||||
@ -38,22 +49,31 @@ export default async function handler(
|
||||
const userId = session.user.id;
|
||||
|
||||
try {
|
||||
const { amount } = req.body as IncreaseBalanceRequestBody;
|
||||
const { amount, isCustom } = req.body as IncreaseBalanceRequestBody;
|
||||
|
||||
// Validierung des Betrags
|
||||
if (typeof amount !== 'number' || !ALLOWED_AMOUNTS[amount]) {
|
||||
return res.status(400).json({
|
||||
message: 'Ungültiger oder nicht erlaubter Betrag.',
|
||||
allowedAmounts: Object.keys(ALLOWED_AMOUNTS).map(Number),
|
||||
});
|
||||
if (typeof amount !== 'number' || amount <= 0) {
|
||||
return res.status(400).json({ message: 'Der Betrag muss eine positive Zahl sein.' });
|
||||
}
|
||||
|
||||
let transactionType: string;
|
||||
const increaseAmountDecimal = new Decimal(amount);
|
||||
const transactionType = ALLOWED_AMOUNTS[amount];
|
||||
|
||||
// Prisma-Transaktion verwenden, um sicherzustellen, dass beide Operationen atomar sind
|
||||
if (isCustom) {
|
||||
// Für individuelle Beträge
|
||||
transactionType = `deposit_custom_${amount.toFixed(2)}`; // oder einfach "deposit_custom"
|
||||
} else {
|
||||
// Für vordefinierte Beträge
|
||||
if (!ALLOWED_PREDEFINED_AMOUNTS[amount]) {
|
||||
return res.status(400).json({
|
||||
message: 'Ungültiger oder nicht erlaubter vordefinierter Betrag.',
|
||||
allowedAmounts: Object.keys(ALLOWED_PREDEFINED_AMOUNTS).map(Number),
|
||||
});
|
||||
}
|
||||
transactionType = ALLOWED_PREDEFINED_AMOUNTS[amount];
|
||||
}
|
||||
|
||||
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
// 1. Benutzer-Saldo aktualisieren
|
||||
const updatedUser = await tx.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
@ -61,10 +81,9 @@ export default async function handler(
|
||||
increment: increaseAmountDecimal,
|
||||
},
|
||||
},
|
||||
select: { // Nur die benötigten Felder auswählen
|
||||
select: {
|
||||
id: true,
|
||||
balance: true,
|
||||
name: true,
|
||||
}
|
||||
});
|
||||
|
||||
@ -72,13 +91,12 @@ export default async function handler(
|
||||
throw new Error('Benutzer nicht gefunden oder Update fehlgeschlagen.');
|
||||
}
|
||||
|
||||
// 2. Transaktion erstellen
|
||||
const newTransaction = await tx.transaction.create({
|
||||
data: {
|
||||
amount: increaseAmountDecimal,
|
||||
type: transactionType,
|
||||
userId: userId,
|
||||
// description: `Einzahlung von ${amount.toFixed(2)}€`, // Optionale Beschreibung
|
||||
description: `Betrag hinzugefügt: ${increaseAmountDecimal.toFixed(2)}€`,
|
||||
},
|
||||
});
|
||||
|
||||
@ -86,17 +104,19 @@ export default async function handler(
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
message: `Saldo erfolgreich um ${amount.toFixed(2)}€ erhöht.`,
|
||||
newBalance: result.updatedUser.balance,
|
||||
message: `Betrag erfolgreich um ${amount.toFixed(2)}€ hinzugefügt.`,
|
||||
newBalance: result.updatedUser.balance?.toFixed(2),
|
||||
transactionId: result.newTransaction.id,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erhöhen des Saldos:', error);
|
||||
// Spezifischere Fehlerbehandlung basierend auf dem Fehlerobjekt
|
||||
let errorMessage = 'Interner Serverfehler beim Erhöhen des Saldos.';
|
||||
if (error instanceof Error && error.message.includes('Benutzer nicht gefunden')) {
|
||||
return res.status(404).json({ message: 'Benutzer nicht gefunden.' });
|
||||
errorMessage = 'Benutzer nicht gefunden.';
|
||||
return res.status(404).json({ message: errorMessage });
|
||||
}
|
||||
return res.status(500).json({ message: 'Interner Serverfehler beim Erhöhen des Saldos.' });
|
||||
// Hier könntest du weitere spezifische Fehlerbehandlungen hinzufügen
|
||||
return res.status(500).json({ message: errorMessage });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ import { useSession, signOut } from 'next-auth/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
// Typ für Transaktionen, wie sie vom Backend (API) erwartet werden
|
||||
interface DashboardTransaction {
|
||||
id: string;
|
||||
userId: string;
|
||||
@ -47,6 +46,10 @@ const DashboardPage: NextPage = () => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||
|
||||
const [customAmount, setCustomAmount] = useState<string>('');
|
||||
const [isSubmittingCustom, setIsSubmittingCustom] = useState(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'unauthenticated') {
|
||||
router.push('/auth/signin');
|
||||
@ -111,16 +114,18 @@ const DashboardPage: NextPage = () => {
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
const handleIncreaseBalance = async (amount: number) => {
|
||||
const handleAddExpense = async (amountValue: number, isCustomAmount: boolean = false) => {
|
||||
setError(null);
|
||||
setSuccessMessage(null);
|
||||
if (isCustomAmount) setIsSubmittingCustom(true);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/user/increase-balance', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ amount }),
|
||||
body: JSON.stringify({ amount: amountValue, isCustom: isCustomAmount }),
|
||||
});
|
||||
const data: IncreaseBalanceApiResponse = await response.json();
|
||||
if (!response.ok) {
|
||||
@ -128,6 +133,7 @@ const DashboardPage: NextPage = () => {
|
||||
}
|
||||
setBalance(data.newBalance);
|
||||
setSuccessMessage(data.message);
|
||||
if (isCustomAmount) setCustomAmount('');
|
||||
await fetchTransactions();
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
@ -136,9 +142,21 @@ const DashboardPage: NextPage = () => {
|
||||
setError('Unbekannter Fehler beim Hinzufügen der Ausgabe aufgetreten.');
|
||||
}
|
||||
console.error("Fehler beim Hinzufügen:", err);
|
||||
} finally {
|
||||
if (isCustomAmount) setIsSubmittingCustom(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCustomAmountSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const amountNumber = parseFloat(customAmount);
|
||||
if (isNaN(amountNumber) || amountNumber <= 0) {
|
||||
setError("Bitte einen gültigen positiven Betrag eingeben.");
|
||||
return;
|
||||
}
|
||||
handleAddExpense(amountNumber, true);
|
||||
};
|
||||
|
||||
const predefinedAmounts = [0.10, 0.20, 0.50, 1.00, 2.00];
|
||||
|
||||
if (status === 'loading') {
|
||||
@ -159,13 +177,12 @@ const DashboardPage: NextPage = () => {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 dark:bg-slate-900 p-4 sm:p-6 lg:p-8 transition-colors duration-300">
|
||||
{/* Header angepasst für mobile Ansicht */}
|
||||
<header className="mb-8 flex flex-col sm:flex-row sm:justify-between sm:items-center">
|
||||
<div className="mb-4 sm:mb-0"> {/* Div für Willkommenstext */}
|
||||
<div className="mb-4 sm:mb-0">
|
||||
<h1 className="text-3xl font-bold text-gray-800 dark:text-gray-100">Willkommen, {userName || session.user.name}!</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400">Deine Strichliste</p>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 sm:flex-row sm:space-y-0 sm:space-x-4"> {/* Container für Buttons */}
|
||||
<div className="flex flex-col space-y-2 sm:flex-row sm:space-y-0 sm:space-x-4">
|
||||
{session?.user?.role === 'admin' && (
|
||||
<button
|
||||
onClick={() => router.push('/admin')}
|
||||
@ -199,17 +216,42 @@ const DashboardPage: NextPage = () => {
|
||||
|
||||
<section className="mb-8 p-6 bg-white dark:bg-slate-800 shadow-lg rounded-lg">
|
||||
<h2 className="text-2xl font-semibold text-gray-700 dark:text-gray-200 mb-4">Ausgaben hinzufügen</h2>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-4">
|
||||
<div className="mb-6 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-4">
|
||||
{predefinedAmounts.map((amount) => (
|
||||
<button
|
||||
key={amount}
|
||||
onClick={() => handleIncreaseBalance(amount)}
|
||||
onClick={() => handleAddExpense(amount)}
|
||||
className="px-4 py-3 bg-indigo-600 text-white font-medium rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150 dark:bg-indigo-500 dark:hover:bg-indigo-600"
|
||||
>
|
||||
+ {amount.toFixed(2)} €
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<form onSubmit={handleCustomAmountSubmit} className="mt-6 border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<label htmlFor="customAmount" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Individueller Betrag:
|
||||
</label>
|
||||
<div className="mt-1 flex rounded-md shadow-sm">
|
||||
<input
|
||||
type="number"
|
||||
name="customAmount"
|
||||
id="customAmount"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
value={customAmount}
|
||||
onChange={(e) => setCustomAmount(e.target.value)}
|
||||
className="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300 dark:border-gray-600 dark:bg-slate-700 dark:text-gray-100 px-3 py-2"
|
||||
placeholder="z.B. 1.25"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmittingCustom || !customAmount}
|
||||
className="inline-flex items-center px-4 py-2 border border-l-0 border-indigo-600 dark:border-indigo-500 rounded-r-md bg-indigo-600 dark:bg-indigo-500 text-white hover:bg-indigo-700 dark:hover:bg-indigo-600 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 disabled:opacity-50"
|
||||
>
|
||||
Hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section className="p-6 bg-white dark:bg-slate-800 shadow-lg rounded-lg">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user