feat(crm): implement bulk mail action for contacts
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 7s
Build & Deploy / 🏗️ Build (push) Failing after 6m25s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 QA (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 7s
Build & Deploy / 🏗️ Build (push) Failing after 6m25s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 QA (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
- Add BulkMailButton component for Payload list view - Add bulkMailEndpoint handler for processing prompts - Integrate bulk mail action into CrmContacts collection - Update dev:clean script to skip interactive prompts
This commit is contained in:
147
apps/web/src/payload/components/BulkMailButton.tsx
Normal file
147
apps/web/src/payload/components/BulkMailButton.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { useSelection, Button, toast } from "@payloadcms/ui";
|
||||
|
||||
export const BulkMailButton: React.FC = () => {
|
||||
const { selected } = useSelection();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [isTest, setIsTest] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const selectedCount = Object.keys(selected).length;
|
||||
|
||||
if (selectedCount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleBulkMail = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const contactIds = Object.keys(selected);
|
||||
|
||||
const response = await fetch("/api/crm-contacts/bulk-mail", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
contactIds,
|
||||
instructions: prompt,
|
||||
isTest,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
toast.success(
|
||||
`Successfully sent emails to ${contactIds.length} contacts.`,
|
||||
);
|
||||
setIsOpen(false);
|
||||
} else {
|
||||
toast.error(`Failed: ${data.error}`);
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast.error(`An error occurred: ${e.message}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: "inline-block", marginLeft: "1rem" }}>
|
||||
<Button
|
||||
onClick={() => setIsOpen(true)}
|
||||
buttonStyle="primary"
|
||||
size="small"
|
||||
>
|
||||
🤖 AI Bulk Mail ({selectedCount})
|
||||
</Button>
|
||||
|
||||
{isOpen && (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "rgba(0,0,0,0.5)",
|
||||
zIndex: 9999,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
background: "var(--theme-elevation-100, #fff)",
|
||||
color: "var(--theme-text, #000)",
|
||||
padding: "2rem",
|
||||
borderRadius: "8px",
|
||||
width: "500px",
|
||||
maxWidth: "90vw",
|
||||
}}
|
||||
>
|
||||
<h2 style={{ marginTop: 0 }}>AI Bulk Mail</h2>
|
||||
<p>
|
||||
Generate and send personalized emails to {selectedCount} contacts.
|
||||
</p>
|
||||
|
||||
<div style={{ marginBottom: "1rem" }}>
|
||||
<label style={{ display: "block", marginBottom: "0.5rem" }}>
|
||||
<strong>Prompt / Instructions for AI</strong>
|
||||
</label>
|
||||
<textarea
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
placeholder="e.g. Mache ein Angebot für ein neues Messe-Design"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100px",
|
||||
padding: "0.5rem",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid var(--theme-elevation-400, #ccc)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "1.5rem" }}>
|
||||
<label
|
||||
style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isTest}
|
||||
onChange={(e) => setIsTest(e.target.checked)}
|
||||
/>
|
||||
<strong>Test Mode</strong> (Send all emails to yourself)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "1rem",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
onClick={() => setIsOpen(false)}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleBulkMail} disabled={isLoading || !prompt}>
|
||||
{isLoading ? "Sending..." : "Send Emails"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user