From 5a26343ff25f9e4729f4eff2f086beae8b647003 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 13:49:16 +0000 Subject: [PATCH 1/4] Changes Co-authored-by: wolfoftyreso-debug <250630591+wolfoftyreso-debug@users.noreply.github.com> --- .../functions/send-outreach-email/index.ts | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 supabase/functions/send-outreach-email/index.ts diff --git a/supabase/functions/send-outreach-email/index.ts b/supabase/functions/send-outreach-email/index.ts new file mode 100644 index 0000000..fd843d5 --- /dev/null +++ b/supabase/functions/send-outreach-email/index.ts @@ -0,0 +1,87 @@ +import { z } from "npm:zod@3.23.8"; + +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": + "authorization, x-client-info, apikey, content-type", + "Access-Control-Allow-Methods": "POST, OPTIONS", +}; + +const GATEWAY_URL = "https://connector-gateway.lovable.dev/brevo"; + +const BodySchema = z.object({ + to_email: z.string().email(), + to_name: z.string().min(1).max(200), + subject: z.string().min(1).max(300), + body: z.string().min(1).max(10000), +}); + +Deno.serve(async (req) => { + if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders }); + + try { + const LOVABLE_API_KEY = Deno.env.get("LOVABLE_API_KEY"); + const BREVO_API_KEY = Deno.env.get("BREVO_API_KEY"); + if (!LOVABLE_API_KEY || !BREVO_API_KEY) { + throw new Error("Email service not configured"); + } + + const parsed = BodySchema.safeParse(await req.json()); + if (!parsed.success) { + return new Response( + JSON.stringify({ error: "Ogiltiga fält", details: parsed.error.flatten().fieldErrors }), + { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }, + ); + } + const { to_email, to_name, subject, body } = parsed.data; + + const senderEmail = Deno.env.get("BREVO_SENDER_EMAIL") ?? "konditorivaror@gmail.com"; + const senderName = "Tiffany – Lennart Svenssons Konditorivaror"; + + // Konvertera radbrytningar till
för HTML + const htmlBody = ` +
+ ${body + .split(/\n{2,}/) + .map((p) => `

${p.replace(/\n/g, "
")}

`) + .join("")} +
`; + + const res = await fetch(`${GATEWAY_URL}/v3/smtp/email`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${LOVABLE_API_KEY}`, + "X-Connection-Api-Key": BREVO_API_KEY, + }, + body: JSON.stringify({ + sender: { name: senderName, email: senderEmail }, + to: [{ email: to_email, name: to_name }], + replyTo: { email: senderEmail, name: senderName }, + subject, + htmlContent: htmlBody, + textContent: body, + }), + }); + + if (!res.ok) { + const errBody = await res.text(); + console.error(`Brevo send failed [${res.status}]:`, errBody); + return new Response( + JSON.stringify({ error: "Brevo avvisade mejlet", status: res.status, details: errBody }), + { status: res.status, headers: { ...corsHeaders, "Content-Type": "application/json" } }, + ); + } + + const data = await res.json(); + return new Response(JSON.stringify({ success: true, messageId: data.messageId }), { + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + } catch (err: any) { + console.error("send-outreach-email error:", err); + return new Response( + JSON.stringify({ error: err?.message ?? "Kunde inte skicka mejl" }), + { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }, + ); + } +}); From c30f4fa767a4c95e3539b9674c4f153d05bda18b Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 13:49:25 +0000 Subject: [PATCH 2/4] Changes Co-authored-by: wolfoftyreso-debug <250630591+wolfoftyreso-debug@users.noreply.github.com> --- src/components/admin/LeadsGenerator.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/admin/LeadsGenerator.tsx b/src/components/admin/LeadsGenerator.tsx index e115ca1..de54081 100644 --- a/src/components/admin/LeadsGenerator.tsx +++ b/src/components/admin/LeadsGenerator.tsx @@ -1,10 +1,11 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; -import { Sparkles, RefreshCw, Building2, Users, MapPin, Mail, Search, Copy, Check } from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { Sparkles, RefreshCw, Building2, Users, MapPin, Mail, Search, Copy, Check, Send } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { Skeleton } from "@/components/ui/skeleton"; From 341739bd14e5f1af391fba73d88a9ebca1b21881 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 13:49:34 +0000 Subject: [PATCH 3/4] Changes Co-authored-by: wolfoftyreso-debug <250630591+wolfoftyreso-debug@users.noreply.github.com> --- src/components/admin/LeadsGenerator.tsx | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/components/admin/LeadsGenerator.tsx b/src/components/admin/LeadsGenerator.tsx index de54081..8c88419 100644 --- a/src/components/admin/LeadsGenerator.tsx +++ b/src/components/admin/LeadsGenerator.tsx @@ -35,8 +35,52 @@ export function LeadsGenerator() { const [researchingLead, setResearchingLead] = useState(null); const [emailDialog, setEmailDialog] = useState(null); const [copiedField, setCopiedField] = useState(null); + const [recipient, setRecipient] = useState(""); + const [sending, setSending] = useState(false); const { toast } = useToast(); + useEffect(() => { + setRecipient(""); + }, [emailDialog?.company_name]); + + const sendEmail = async () => { + if (!emailDialog || !recipient) return; + setSending(true); + try { + const res = await fetch( + `${import.meta.env.VITE_SUPABASE_URL}/functions/v1/send-outreach-email`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY}`, + }, + body: JSON.stringify({ + to_email: recipient, + to_name: emailDialog.company_name, + subject: emailDialog.email.subject, + body: emailDialog.email.body, + }), + }, + ); + const data = await res.json(); + if (!res.ok) throw new Error(data.error || "Kunde inte skicka mejl"); + toast({ + title: "Mejl skickat!", + description: `Skickat från konditorivaror@gmail.com till ${recipient}`, + }); + setEmailDialog(null); + } catch (err) { + toast({ + title: "Fel vid utskick", + description: err instanceof Error ? err.message : "Okänt fel", + variant: "destructive", + }); + } finally { + setSending(false); + } + }; + const generateLeads = async () => { setIsLoading(true); try { From 2340592f282ca103458da6073df765b125ac02f2 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 13:49:50 +0000 Subject: [PATCH 4/4] Changes Co-authored-by: wolfoftyreso-debug <250630591+wolfoftyreso-debug@users.noreply.github.com> --- src/components/admin/LeadsGenerator.tsx | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/components/admin/LeadsGenerator.tsx b/src/components/admin/LeadsGenerator.tsx index 8c88419..e4ade47 100644 --- a/src/components/admin/LeadsGenerator.tsx +++ b/src/components/admin/LeadsGenerator.tsx @@ -363,6 +363,32 @@ export function LeadsGenerator() { )} + +
+ +
+ setRecipient(e.target.value)} + disabled={sending} + /> + +
+

+ Skickas från konditorivaror@gmail.com som Tiffany. +

+
)}