mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 20:51:17 +00:00
see comments
This commit is contained in:
parent
a9fdcf9fe5
commit
6dd4587818
9 changed files with 253 additions and 57 deletions
|
|
@ -5,36 +5,79 @@ import { revalidatePath } from "next/cache";
|
|||
import { createAdminClient } from "@/lib/supabase/admin";
|
||||
import { verifySession } from "@/lib/supabase/dal";
|
||||
|
||||
const StammdatenSchema = z.object({
|
||||
profileId: z.string().uuid(),
|
||||
const IBAN_REGEX = /^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/;
|
||||
|
||||
companyName: z.string().min(1, { error: "Firmenname ist erforderlich." }),
|
||||
legalForm: z.string().min(1, { error: "Rechtsform ist erforderlich." }),
|
||||
industry: z.string().optional(),
|
||||
vatId: z.string().optional(),
|
||||
taxNumber: z.string().optional(),
|
||||
commercialRegisterNumber: z.string().optional(),
|
||||
const StammdatenSchema = z
|
||||
.object({
|
||||
profileId: z.string().uuid(),
|
||||
|
||||
street: z.string().min(1, { error: "Straße ist erforderlich." }),
|
||||
postalCode: z.string().min(1, { error: "PLZ ist erforderlich." }),
|
||||
city: z.string().min(1, { error: "Stadt ist erforderlich." }),
|
||||
country: z.string().length(2).default("DE"),
|
||||
companyName: z.string().min(1, { error: "Firmenname ist erforderlich." }),
|
||||
legalForm: z.string().min(1, { error: "Rechtsform ist erforderlich." }),
|
||||
industry: z.string().optional(),
|
||||
vatId: z.string().optional(),
|
||||
taxNumber: z.string().optional(),
|
||||
commercialRegisterNumber: z.string().optional(),
|
||||
|
||||
billingStreet: z.string().optional(),
|
||||
billingPostalCode: z.string().optional(),
|
||||
billingCity: z.string().optional(),
|
||||
billingCountry: z.string().optional(),
|
||||
street: z.string().min(1, { error: "Straße ist erforderlich." }),
|
||||
postalCode: z.string().min(1, { error: "PLZ ist erforderlich." }),
|
||||
city: z.string().min(1, { error: "Stadt ist erforderlich." }),
|
||||
country: z.string().length(2).default("DE"),
|
||||
|
||||
contactName: z.string().min(1, { error: "Ansprechpartner ist erforderlich." }),
|
||||
contactPosition: z.string().optional(),
|
||||
contactEmail: z.email({ error: "Bitte eine gültige Email eingeben." }),
|
||||
contactPhone: z.string().optional(),
|
||||
billingStreet: z.string().optional(),
|
||||
billingPostalCode: z.string().optional(),
|
||||
billingCity: z.string().optional(),
|
||||
billingCountry: z.string().optional(),
|
||||
|
||||
paymentMethod: z.enum(["invoice", "sepa_debit", "card"]).default("invoice"),
|
||||
accountHolder: z.string().optional(),
|
||||
iban: z.string().optional(),
|
||||
bic: z.string().optional(),
|
||||
});
|
||||
contactName: z.string().min(1, { error: "Ansprechpartner ist erforderlich." }),
|
||||
contactPosition: z.string().optional(),
|
||||
contactEmail: z.email({ error: "Bitte eine gültige Email eingeben." }),
|
||||
contactPhone: z.string().optional(),
|
||||
|
||||
paymentMethod: z.enum(["invoice", "sepa_debit", "card"]).default("invoice"),
|
||||
accountHolder: z.string().optional(),
|
||||
iban: z.string().optional(),
|
||||
bic: z.string().optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.paymentMethod === "sepa_debit") {
|
||||
if (!data.accountHolder) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
path: ["accountHolder"],
|
||||
message: "Kontoinhaber ist bei SEPA-Lastschrift erforderlich.",
|
||||
});
|
||||
}
|
||||
if (!data.iban) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
path: ["iban"],
|
||||
message: "IBAN ist bei SEPA-Lastschrift erforderlich.",
|
||||
});
|
||||
} else if (!IBAN_REGEX.test(data.iban)) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
path: ["iban"],
|
||||
message: "Bitte eine gültige IBAN eingeben.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const billingFields = [
|
||||
data.billingStreet,
|
||||
data.billingPostalCode,
|
||||
data.billingCity,
|
||||
data.billingCountry,
|
||||
];
|
||||
const filledCount = billingFields.filter(Boolean).length;
|
||||
|
||||
if (filledCount > 0 && filledCount < billingFields.length) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
path: ["billingStreet"],
|
||||
message: "Bitte alle Felder der Rechnungsadresse ausfüllen oder alle leer lassen.",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type StammdatenState = { error: string } | { success: true } | undefined;
|
||||
|
||||
|
|
@ -141,22 +184,31 @@ export async function saveStammdaten(
|
|||
const { error } = await supabase.from("clients").update(clientPayload).eq("id", clientId);
|
||||
if (error) return { error: "Stammdaten konnten nicht gespeichert werden." };
|
||||
} else {
|
||||
const { data: newClient, error } = await supabase
|
||||
.from("clients")
|
||||
.insert(clientPayload)
|
||||
.select("id")
|
||||
.single();
|
||||
const { data: newClientId, error } = await supabase.rpc("create_client_and_link_profile", {
|
||||
p_profile_id: data.profileId,
|
||||
p_company_name: clientPayload.company_name,
|
||||
p_legal_form: clientPayload.legal_form,
|
||||
p_industry: clientPayload.industry,
|
||||
p_vat_id: clientPayload.vat_id,
|
||||
p_tax_number: clientPayload.tax_number,
|
||||
p_commercial_register_number: clientPayload.commercial_register_number,
|
||||
p_street: clientPayload.street,
|
||||
p_postal_code: clientPayload.postal_code,
|
||||
p_city: clientPayload.city,
|
||||
p_country: clientPayload.country,
|
||||
p_billing_street: clientPayload.billing_street,
|
||||
p_billing_postal_code: clientPayload.billing_postal_code,
|
||||
p_billing_city: clientPayload.billing_city,
|
||||
p_billing_country: clientPayload.billing_country,
|
||||
p_contact_name: clientPayload.contact_name,
|
||||
p_contact_position: clientPayload.contact_position,
|
||||
p_contact_email: clientPayload.contact_email,
|
||||
p_contact_phone: clientPayload.contact_phone,
|
||||
});
|
||||
|
||||
if (error || !newClient) return { error: "Stammdaten konnten nicht gespeichert werden." };
|
||||
if (error || !newClientId) return { error: "Stammdaten konnten nicht gespeichert werden." };
|
||||
|
||||
clientId = newClient.id;
|
||||
|
||||
const { error: linkError } = await supabase
|
||||
.from("profiles")
|
||||
.update({ client_id: clientId })
|
||||
.eq("id", data.profileId);
|
||||
|
||||
if (linkError) return { error: "Verknüpfung mit Profil fehlgeschlagen." };
|
||||
clientId = newClientId;
|
||||
}
|
||||
|
||||
const { error: paymentError } = await supabase.from("client_payment_details").upsert({
|
||||
|
|
|
|||
|
|
@ -16,10 +16,14 @@ export async function getStammdaten(profileId: string) {
|
|||
return { client: null, paymentDetails: null }
|
||||
}
|
||||
|
||||
const [{ data: client }, { data: paymentDetails }] = await Promise.all([
|
||||
supabase.from("clients").select("*").eq("id", profile.client_id).single(),
|
||||
supabase.from("client_payment_details").select("*").eq("client_id", profile.client_id).single(),
|
||||
const [clientRes, paymentRes] = await Promise.all([
|
||||
supabase.from("clients").select("*").eq("id", profile.client_id).maybeSingle(),
|
||||
supabase.from("client_payment_details").select("*").eq("client_id", profile.client_id).maybeSingle(),
|
||||
])
|
||||
|
||||
return { client: client ?? null, paymentDetails: paymentDetails ?? null }
|
||||
if (clientRes.error || paymentRes.error) {
|
||||
throw new Error("Stammdaten konnten nicht geladen werden.")
|
||||
}
|
||||
|
||||
return { client: clientRes.data, paymentDetails: paymentRes.data }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@ export function StammdatenPanel({
|
|||
paymentDetails: PaymentDetails;
|
||||
}) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [resetCount, setResetCount] = useState(0);
|
||||
|
||||
function handleDone() {
|
||||
setIsEditing(false);
|
||||
setResetCount((count) => count + 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8 w-full">
|
||||
|
|
@ -30,12 +36,12 @@ export function StammdatenPanel({
|
|||
)}
|
||||
</div>
|
||||
<StammdatenForm
|
||||
key={JSON.stringify({ client, paymentDetails })}
|
||||
key={`${resetCount}-${JSON.stringify({ client, paymentDetails })}`}
|
||||
profileId={profileId}
|
||||
client={client}
|
||||
paymentDetails={paymentDetails}
|
||||
isEditing={isEditing}
|
||||
onDone={() => setIsEditing(false)}
|
||||
onDone={handleDone}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,18 +10,28 @@ import { Button } from "@/components/ui/button";
|
|||
export function TermsDialog() {
|
||||
const [open, setOpen] = useState(true);
|
||||
const [pending, setPending] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
async function handleAccept() {
|
||||
setPending(true);
|
||||
const result = await acceptTerms();
|
||||
setError(null);
|
||||
|
||||
if (result && "success" in result) {
|
||||
setOpen(false);
|
||||
router.refresh();
|
||||
try {
|
||||
const result = await acceptTerms();
|
||||
|
||||
if (result && "success" in result) {
|
||||
setOpen(false);
|
||||
router.refresh();
|
||||
} else {
|
||||
setError(result?.error ?? "Zustimmung konnte nicht gespeichert werden.");
|
||||
}
|
||||
} catch {
|
||||
setError("Zustimmung konnte nicht gespeichert werden. Bitte erneut versuchen.");
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
setPending(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={() => {}}>
|
||||
|
|
@ -29,9 +39,14 @@ export function TermsDialog() {
|
|||
<DialogHeader>
|
||||
<DialogTitle>Nutzungsbedingungen</DialogTitle>
|
||||
<DialogDescription>
|
||||
Bitte akzeptiere unsere Nutzungsbedingungen, um fortzufahren.
|
||||
Bitte lies unsere{" "}
|
||||
<a href="/terms" target="_blank" rel="noopener noreferrer">
|
||||
Nutzungsbedingungen
|
||||
</a>{" "}
|
||||
und akzeptiere sie, um fortzufahren.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||
<DialogFooter>
|
||||
<Button onClick={handleAccept} disabled={pending}>
|
||||
{pending ? "..." : "Akzeptieren"}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue