feat: send statements through Gmail SMTP
Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
312f061ad7
commit
b92a5b2817
2 changed files with 5 additions and 64 deletions
|
|
@ -3,67 +3,20 @@
|
|||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import PocketBase from "pocketbase";
|
||||
import { Resend } from "resend";
|
||||
import { pbCreate, pbList, pbUpdate } from "@/lib/pocketbase";
|
||||
import { sendEmail } from "@/lib/email";
|
||||
import { getAuth } from "@/lib/auth";
|
||||
import { pbId } from "@/lib/pb";
|
||||
import { PB_URL } from "@/lib/pocketbase";
|
||||
|
||||
export type ActionResult = { ok: true } | { ok: false; error: string };
|
||||
|
||||
const emailSettingsSchema = z.object({
|
||||
statement_from_email: z.string().email("Valid email").or(z.literal("")).optional(),
|
||||
statement_cc_email: z.string().email("Valid email").or(z.literal("")).optional(),
|
||||
statement_app_email: z.string().email("Valid email").or(z.literal("")).optional(),
|
||||
statement_email_password: z.string().optional(),
|
||||
});
|
||||
|
||||
function clean(obj: any) {
|
||||
const out: any = {};
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
if (v === "" || v === null || v === undefined) continue;
|
||||
out[k] = v;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export async function updateEmailSettings(_: unknown, formData: FormData): Promise<ActionResult> {
|
||||
const { token } = await getAuth();
|
||||
if (!token) return { ok: false, error: "Not signed in" };
|
||||
const parsed = emailSettingsSchema.safeParse({
|
||||
statement_from_email: formData.get("statement_from_email") || "",
|
||||
statement_cc_email: formData.get("statement_cc_email") || "",
|
||||
statement_app_email: formData.get("statement_app_email") || "",
|
||||
statement_email_password: formData.get("statement_email_password") || "",
|
||||
});
|
||||
if (!parsed.success) return { ok: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };
|
||||
try {
|
||||
const settings = await pbList<any>("fuel_settings", { perPage: 1 }, token);
|
||||
if (settings.items.length === 0) return { ok: false, error: "Settings not configured" };
|
||||
await pbUpdate("fuel_settings", settings.items[0].id, clean(parsed.data), token);
|
||||
} catch (err: any) {
|
||||
return { ok: false, error: err?.message || "Failed to update" };
|
||||
}
|
||||
revalidatePath("/settings");
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
export async function sendTestEmail(to: string): Promise<ActionResult> {
|
||||
if (!to) return { ok: false, error: "Recipient email required" };
|
||||
const apiKey = process.env.RESEND_API_KEY;
|
||||
if (!apiKey) return { ok: false, error: "RESEND_API_KEY not configured" };
|
||||
const fromAddr = process.env.RESEND_FROM || "statements@rjbulk.co.za";
|
||||
try {
|
||||
const resend = new Resend(apiKey);
|
||||
const result = await resend.emails.send({
|
||||
from: fromAddr,
|
||||
await sendEmail({
|
||||
to,
|
||||
subject: "RJ Bulk test email",
|
||||
html: "<p>This is a test email from your RJ Bulk back-office. If you got this, email is working correctly.</p>",
|
||||
});
|
||||
if (result.error) {
|
||||
return { ok: false, error: result.error.message || "Send failed" };
|
||||
}
|
||||
} catch (err: any) {
|
||||
return { ok: false, error: err?.message || "Send failed" };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import { Resend } from "resend";
|
||||
import { sendEmail } from "@/lib/email";
|
||||
import { pbCreate, pbList, pbUpdate } from "@/lib/pocketbase";
|
||||
import { getAuth } from "@/lib/auth";
|
||||
import { pbId } from "@/lib/pb";
|
||||
|
|
@ -105,20 +105,12 @@ export async function sendStatement(_: unknown, formData: FormData): Promise<Act
|
|||
companySettings: settingsRow,
|
||||
});
|
||||
|
||||
// Email via Resend
|
||||
const apiKey = process.env.RESEND_API_KEY;
|
||||
if (!apiKey) return { ok: false, error: "RESEND_API_KEY not configured" };
|
||||
const fromAddr = process.env.RESEND_FROM || settingsRow?.statement_from_email || "statements@rjbulk.co.za";
|
||||
const resend = new Resend(apiKey);
|
||||
const cc = settingsRow?.statement_cc_email ? [settingsRow.statement_cc_email] : undefined;
|
||||
|
||||
// Email via Gmail SMTP
|
||||
const totalQty = notes.items.reduce((a, n) => a + n.qty_litres_supplied, 0);
|
||||
const totalValue = notes.items.reduce((a, n) => a + (n.total_value || 0), 0);
|
||||
|
||||
const result = await resend.emails.send({
|
||||
from: fromAddr,
|
||||
await sendEmail({
|
||||
to: parsed.data.recipient_email,
|
||||
cc,
|
||||
subject: `Delivery Statement — ${cust.name} (${start} to ${end})`,
|
||||
html: `<p>Hi ${cust.name},</p>
|
||||
<p>Please find your delivery statement attached for the period <strong>${start}</strong> to <strong>${end}</strong>.</p>
|
||||
|
|
@ -134,10 +126,6 @@ Total value: <strong>R ${totalValue.toFixed(2)}</strong></p>
|
|||
],
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
return { ok: false, error: result.error.message || "Email send failed" };
|
||||
}
|
||||
|
||||
// Notes stay "Pending" — the customer pays separately (EFT / CASH / CARD).
|
||||
// We don't auto-flip the status here; the owner marks notes as paid manually.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue