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
14e1f03b66
commit
c700a1b0e2
4 changed files with 68 additions and 46 deletions
|
|
@ -55,13 +55,19 @@ export async function createClient(
|
||||||
return { error: error.code === "email_exists" ? "Diese Email ist bereits vergeben." : "Client konnte nicht angelegt werden." }
|
return { error: error.code === "email_exists" ? "Diese Email ist bereits vergeben." : "Client konnte nicht angelegt werden." }
|
||||||
}
|
}
|
||||||
|
|
||||||
const mail = welcomeMail({ name, email, appUrl: await getAppUrl() });
|
// Der Account existiert bereits: ein Mailfehler darf nicht verhindern, dass das Passwort angezeigt wird.
|
||||||
const sent = await sendMail({
|
let welcomeMailSent = false;
|
||||||
to: email,
|
try {
|
||||||
...mail,
|
const sent = await sendMail({
|
||||||
type: "welcome",
|
to: email,
|
||||||
idempotencyKey: `welcome-${data.user.id}`,
|
...welcomeMail({ name, email, appUrl: await getAppUrl() }),
|
||||||
});
|
type: "welcome",
|
||||||
|
idempotencyKey: `welcome-${data.user.id}`,
|
||||||
|
});
|
||||||
|
welcomeMailSent = sent.ok;
|
||||||
|
} catch (mailError) {
|
||||||
|
console.error("[mail] Willkommensmail fehlgeschlagen:", mailError);
|
||||||
|
}
|
||||||
|
|
||||||
return { success: true, email, password, welcomeMailSent: sent.ok }
|
return { success: true, email, password, welcomeMailSent }
|
||||||
}
|
}
|
||||||
|
|
@ -63,17 +63,20 @@ export async function createCredential(
|
||||||
|
|
||||||
// Nur wenn wir den Zugang hinterlegen – legt der Kunde ihn selbst an, braucht er keine Mail.
|
// Nur wenn wir den Zugang hinterlegen – legt der Kunde ihn selbst an, braucht er keine Mail.
|
||||||
if (session.isAdmin) {
|
if (session.isAdmin) {
|
||||||
const appUrl = await getAppUrl();
|
|
||||||
const serviceLabel = getServiceById(validatedFields.data.service).label;
|
const serviceLabel = getServiceById(validatedFields.data.service).label;
|
||||||
after(async () => {
|
after(async () => {
|
||||||
const recipient = await getMailRecipient(profileId);
|
try {
|
||||||
if (!recipient) return;
|
const recipient = await getMailRecipient(profileId);
|
||||||
await sendMail({
|
if (!recipient) return;
|
||||||
to: recipient.email,
|
await sendMail({
|
||||||
...credentialAddedMail({ name: recipient.name, serviceLabel, appUrl }),
|
to: recipient.email,
|
||||||
type: "credential",
|
...credentialAddedMail({ name: recipient.name, serviceLabel, appUrl: await getAppUrl() }),
|
||||||
idempotencyKey: `credential-${credentialId}`,
|
type: "credential",
|
||||||
});
|
idempotencyKey: `credential-${credentialId}`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[mail] Hinweis-Mail zum neuen Zugang fehlgeschlagen:", error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,34 +41,43 @@ async function deliverInvoiceMail(profileId: string, clientId: string, invoiceId
|
||||||
if (!invoice) return { error: "Rechnung nicht gefunden." };
|
if (!invoice) return { error: "Rechnung nicht gefunden." };
|
||||||
if (invoice.status === "cancelled") return { error: "Stornierte Rechnungen werden nicht verschickt." };
|
if (invoice.status === "cancelled") return { error: "Stornierte Rechnungen werden nicht verschickt." };
|
||||||
|
|
||||||
const [pdfData] = await getInvoicesForPdf([invoiceId]);
|
// Die Rechnung ist beim Aufruf aus createInvoice schon gespeichert: Fehler hier dürfen nicht nach oben durchschlagen.
|
||||||
const recipient = await getMailRecipient(profileId);
|
try {
|
||||||
if (!pdfData || !recipient) return { error: "Rechnung oder Empfänger konnte nicht geladen werden." };
|
const [pdfData] = await getInvoicesForPdf([invoiceId]);
|
||||||
|
const recipient = await getMailRecipient(profileId);
|
||||||
|
if (!pdfData || !recipient) return { error: "Rechnung oder Empfänger konnte nicht geladen werden." };
|
||||||
|
|
||||||
const pdf = await renderInvoicePdf(pdfData);
|
const [pdf, appUrl] = await Promise.all([renderInvoicePdf(pdfData), getAppUrl()]);
|
||||||
const mail = invoiceMail({
|
const mail = invoiceMail({
|
||||||
name: recipient.name,
|
name: recipient.name,
|
||||||
invoiceNumber: pdfData.invoiceNumber,
|
invoiceNumber: pdfData.invoiceNumber,
|
||||||
invoiceDate: pdfData.invoiceDate,
|
invoiceDate: pdfData.invoiceDate,
|
||||||
dueDate: pdfData.dueDate,
|
dueDate: pdfData.dueDate,
|
||||||
grossTotal: invoiceTotals(pdfData.items).gross,
|
grossTotal: invoiceTotals(pdfData.items).gross,
|
||||||
appUrl: await getAppUrl(),
|
appUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sent = await sendMail({
|
const sent = await sendMail({
|
||||||
to: recipient.email,
|
to: recipient.email,
|
||||||
...mail,
|
...mail,
|
||||||
type: "invoice",
|
type: "invoice",
|
||||||
attachments: [{ filename: invoiceFileName(pdfData.invoiceNumber), content: new Uint8Array(pdf), contentType: "application/pdf" }],
|
attachments: [{ filename: invoiceFileName(pdfData.invoiceNumber), content: new Uint8Array(pdf), contentType: "application/pdf" }],
|
||||||
});
|
});
|
||||||
if (!sent.ok) return { error: sent.error };
|
if (!sent.ok) return { error: sent.error };
|
||||||
|
|
||||||
await supabase
|
const { error: updateError } = await supabase
|
||||||
.from("invoices")
|
.from("invoices")
|
||||||
.update({ email_sent_at: new Date().toISOString(), email_sent_to: recipient.email })
|
.update({ email_sent_at: new Date().toISOString(), email_sent_to: recipient.email })
|
||||||
.eq("id", invoiceId);
|
.eq("id", invoiceId);
|
||||||
|
if (updateError) {
|
||||||
|
console.error(`[mail] Rechnung ${invoiceId} wurde versendet, Versandstatus aber nicht gespeichert:`, updateError.message);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[mail] Rechnung ${invoiceId} konnte nicht versendet werden:`, error);
|
||||||
|
return { error: "E-Mail konnte nicht gesendet werden." };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createInvoice(draft: InvoiceDraft, options: { sendEmail?: boolean } = {}): Promise<CreateInvoiceState> {
|
export async function createInvoice(draft: InvoiceDraft, options: { sendEmail?: boolean } = {}): Promise<CreateInvoiceState> {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,19 @@
|
||||||
import "server-only"
|
import "server-only"
|
||||||
import { headers } from "next/headers"
|
import { headers } from "next/headers"
|
||||||
|
|
||||||
// Basis-URL für Links in Mails. APP_URL hat Vorrang, auf Vercel greift die Produktionsdomain, sonst der aktuelle Request.
|
// Basis-URL für Links in Mails. Nie aus Request-Headern in Produktion: Host/X-Forwarded-Host sind vom Client fälschbar.
|
||||||
|
// Reihenfolge: APP_URL, Vercel-Produktionsdomain, Vercel-Deployment-URL (Preview), lokal der Request-Host.
|
||||||
export async function getAppUrl() {
|
export async function getAppUrl() {
|
||||||
if (process.env.APP_URL) return process.env.APP_URL.replace(/\/$/, "")
|
if (process.env.APP_URL) return process.env.APP_URL.replace(/\/$/, "")
|
||||||
if (process.env.VERCEL_ENV === "production" && process.env.VERCEL_PROJECT_PRODUCTION_URL) {
|
if (process.env.VERCEL_ENV === "production" && process.env.VERCEL_PROJECT_PRODUCTION_URL) {
|
||||||
return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
|
return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
|
||||||
}
|
}
|
||||||
|
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`
|
||||||
|
|
||||||
const headerList = await headers()
|
if (process.env.NODE_ENV === "development") {
|
||||||
const host = headerList.get("x-forwarded-host") ?? headerList.get("host") ?? "localhost:3000"
|
const host = (await headers()).get("host") ?? "localhost:3000"
|
||||||
const protocol = headerList.get("x-forwarded-proto") ?? (host.startsWith("localhost") ? "http" : "https")
|
return `http://${host}`
|
||||||
return `${protocol}://${host}`
|
}
|
||||||
|
|
||||||
|
throw new Error("APP_URL ist nicht gesetzt – ohne feste Basis-URL werden keine Mail-Links erzeugt.")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue