mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 20:51:18 +00:00
Merge pull request #121 from Since-3/feature/added-business-settings
added business settings
This commit is contained in:
commit
7aba3b6af7
3 changed files with 306 additions and 2 deletions
55
src/modules/settings/actions/business-settings-actions.ts
Normal file
55
src/modules/settings/actions/business-settings-actions.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
"use server";
|
||||
|
||||
import { prisma } from "@/src/lib/prisma";
|
||||
import { requireManager } from "@/src/lib/auth/getUser";
|
||||
import { revalidateTag } from "next/cache";
|
||||
|
||||
type ActionResult = { success: boolean; error?: string };
|
||||
|
||||
export async function updateBusinessInfo(data: {
|
||||
businessId: string;
|
||||
address: string;
|
||||
email: string;
|
||||
title: string;
|
||||
ustId?: string;
|
||||
banking?: string;
|
||||
}): Promise<ActionResult> {
|
||||
try {
|
||||
const manager = await requireManager();
|
||||
|
||||
const business = await prisma.business.findUnique({
|
||||
where: { id: data.businessId },
|
||||
select: { managerId: true },
|
||||
});
|
||||
|
||||
if (!business || business.managerId !== manager.id) {
|
||||
return { success: false, error: "Nicht berechtigt." };
|
||||
}
|
||||
|
||||
const email = data.email.trim();
|
||||
|
||||
if (!data.address.trim() || !email || !data.title.trim()) {
|
||||
return { success: false, error: "Bitte alle Pflichtfelder ausfüllen." };
|
||||
}
|
||||
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
return { success: false, error: "Ungültige E-Mail-Adresse." };
|
||||
}
|
||||
|
||||
await prisma.business.update({
|
||||
where: { id: data.businessId },
|
||||
data: {
|
||||
address: data.address.trim(),
|
||||
email,
|
||||
title: data.title.trim(),
|
||||
ustId: data.ustId?.trim() || null,
|
||||
banking: data.banking?.trim() || null,
|
||||
},
|
||||
});
|
||||
|
||||
(revalidateTag as (tag: string, type: "max") => void)(`user-data-${manager.email}`, "max");
|
||||
return { success: true };
|
||||
} catch {
|
||||
return { success: false, error: "Fehler beim Speichern der Business-Daten." };
|
||||
}
|
||||
}
|
||||
202
src/modules/settings/ui/components/BusinessSettingsForm.tsx
Normal file
202
src/modules/settings/ui/components/BusinessSettingsForm.tsx
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
"use client";
|
||||
|
||||
import { type ReactNode, useState, useTransition } from "react";
|
||||
import InputComponent from "@/src/components/layout/InputComponent";
|
||||
import { Button } from "@/src/components/ui/button";
|
||||
import { updateBusinessInfo } from "../../actions/business-settings-actions";
|
||||
import { updateProfileInfo } from "../../actions/personal-settings-actions";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export type BusinessSettingsFormProps = {
|
||||
businessId: string;
|
||||
name: string;
|
||||
address: string;
|
||||
email: string;
|
||||
title: string;
|
||||
ustId: string | null;
|
||||
banking: string | null;
|
||||
managerTel: string | null;
|
||||
};
|
||||
|
||||
function Card({
|
||||
title,
|
||||
subtitle,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl shadow-sm p-6 space-y-5">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-gray-900">{title}</h2>
|
||||
{subtitle && <p className="text-xs text-gray-400 mt-0.5">{subtitle}</p>}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const BusinessSettingsForm = ({
|
||||
businessId,
|
||||
name,
|
||||
address: initialAddress,
|
||||
email: initialEmail,
|
||||
title: initialTitle,
|
||||
ustId: initialUstId,
|
||||
banking: initialBanking,
|
||||
managerTel: initialTel,
|
||||
}: BusinessSettingsFormProps) => {
|
||||
const [pending, startTransition] = useTransition();
|
||||
|
||||
const [address, setAddress] = useState(initialAddress);
|
||||
const [email, setEmail] = useState(initialEmail);
|
||||
const [title, setTitle] = useState(initialTitle);
|
||||
const [ustId, setUstId] = useState(initialUstId ?? "");
|
||||
const [banking, setBanking] = useState(initialBanking ?? "");
|
||||
const [tel, setTel] = useState(initialTel ?? "");
|
||||
|
||||
const handleBusinessSave = () => {
|
||||
startTransition(async () => {
|
||||
try {
|
||||
const result = await updateBusinessInfo({
|
||||
businessId,
|
||||
address,
|
||||
email,
|
||||
title,
|
||||
ustId,
|
||||
banking,
|
||||
});
|
||||
if (result.success) {
|
||||
toast.success("Business-Daten erfolgreich gespeichert.");
|
||||
} else {
|
||||
toast.error(result.error ?? "Fehler beim Speichern.");
|
||||
}
|
||||
} catch {
|
||||
toast.error("Fehler beim Speichern.");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleTelSave = () => {
|
||||
startTransition(async () => {
|
||||
try {
|
||||
const result = await updateProfileInfo({ tel });
|
||||
if (result.success) {
|
||||
toast.success("Geschäftsnummer erfolgreich gespeichert.");
|
||||
} else {
|
||||
toast.error(result.error ?? "Fehler beim Speichern.");
|
||||
}
|
||||
} catch {
|
||||
toast.error("Fehler beim Speichern.");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Read-only Unternehmensname */}
|
||||
<div className="border border-white/60 bg-white/55 backdrop-blur-xl rounded-2xl shadow-sm p-6">
|
||||
<p className="text-xs font-semibold tracking-widest text-gray-400 uppercase mb-1">
|
||||
Unternehmensname
|
||||
</p>
|
||||
<p className="text-xl font-bold text-gray-900">{name}</p>
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
Der Unternehmensname kann nach der Registrierung nicht mehr geändert werden.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<Card title="Allgemeine Informationen" subtitle="Titel und Adresse deines Unternehmens">
|
||||
<div className="space-y-4">
|
||||
<InputComponent
|
||||
label="Titel / Kurzname"
|
||||
type="text"
|
||||
id="businessTitle"
|
||||
isLabel
|
||||
placeholder="Kurzbezeichnung"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
<InputComponent
|
||||
label="Adresse"
|
||||
type="text"
|
||||
id="businessAddress"
|
||||
isLabel
|
||||
placeholder="Musterstraße 1, 12345 Musterstadt"
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-1">
|
||||
<Button onClick={handleBusinessSave} disabled={pending} size="sm" className="w-fit">
|
||||
Speichern
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title="Kontakt & Rechtliches" subtitle="E-Mail, USt-IdNr. und Bankverbindung">
|
||||
<div className="space-y-4">
|
||||
<InputComponent
|
||||
label="Geschäfts-E-Mail"
|
||||
type="email"
|
||||
id="businessEmail"
|
||||
isLabel
|
||||
placeholder="info@unternehmen.de"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<InputComponent
|
||||
label="USt-IdNr. (optional)"
|
||||
type="text"
|
||||
id="businessUstId"
|
||||
isLabel
|
||||
placeholder="DE123456789"
|
||||
value={ustId}
|
||||
onChange={(e) => setUstId(e.target.value)}
|
||||
/>
|
||||
<InputComponent
|
||||
label="Bankverbindung (optional)"
|
||||
type="text"
|
||||
id="businessBanking"
|
||||
isLabel
|
||||
placeholder="IBAN / Kontoinhaber"
|
||||
value={banking}
|
||||
onChange={(e) => setBanking(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-1">
|
||||
<Button onClick={handleBusinessSave} disabled={pending} size="sm" className="w-fit">
|
||||
Speichern
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title="Geschäftsnummer" subtitle="Telefonnummer des Unternehmens">
|
||||
<InputComponent
|
||||
label="Telefonnummer"
|
||||
type="tel"
|
||||
id="managerTel"
|
||||
isLabel
|
||||
placeholder="+49 000 000000"
|
||||
value={tel}
|
||||
onChange={(e) => setTel(e.target.value)}
|
||||
/>
|
||||
<div className="pt-1">
|
||||
<Button
|
||||
onClick={handleTelSave}
|
||||
disabled={pending || tel === (initialTel ?? "")}
|
||||
size="sm"
|
||||
className="w-fit"
|
||||
>
|
||||
Speichern
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BusinessSettingsForm;
|
||||
|
|
@ -1,5 +1,52 @@
|
|||
const SettingsBusinessPageView = () => {
|
||||
return <div></div>;
|
||||
import { requireManager } from "@/src/lib/auth/getUser";
|
||||
import { prisma } from "@/src/lib/prisma";
|
||||
import BusinessSettingsForm from "../components/BusinessSettingsForm";
|
||||
|
||||
const SettingsBusinessPageView = async () => {
|
||||
const manager = await requireManager();
|
||||
|
||||
const business = await prisma.business.findFirst({
|
||||
where: { managerId: manager.id },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
address: true,
|
||||
email: true,
|
||||
title: true,
|
||||
ustId: true,
|
||||
banking: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!business) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Business Einstellungen</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">Kein Business gefunden.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Business Einstellungen</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">Verwalte die Daten deines Unternehmens</p>
|
||||
</div>
|
||||
<BusinessSettingsForm
|
||||
businessId={business.id}
|
||||
name={business.name}
|
||||
address={business.address}
|
||||
email={business.email}
|
||||
title={business.title}
|
||||
ustId={business.ustId}
|
||||
banking={business.banking}
|
||||
managerTel={manager.tel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsBusinessPageView;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue