3import React, { useState, useEffect } from "react";
4import Link from "next/link";
6interface MessagingAccount {
7 f100: string; // Account ID
8 f109: string; // Account Name
9 f110: string; // Type (SMTP, POP3, IMAP, SMS, FTP, UPLOAD, AWSS3)
10 f101?: string; // Server
11 f102?: string; // Port
12 f103?: string; // User/Email
13 f104?: string; // Password
14 f111?: string; // Auth schemes
15 f112?: string; // From Name
16 f118?: string; // Reply To
17 f120?: number; // Status flags (1=hold)
18 f120s?: string; // Status string
19 f120s_bkcolor?: string; // Status background color
20 f128?: number; // Well known server ID
21 f129?: string; // Account headers
22 f130?: string; // Provider type (for SMS) or FTP folder
23 f131?: string; // File types (FTP)
24 f132?: string; // Output folder (FTP)
25 f133?: string; // API Key (SMS)
26 f134?: string; // API Secret (SMS)
27 f140?: string; // Bucket name (S3) or Country code (SMS)
28 f141?: string; // Access key (S3)
29 f142?: string; // Secret key (S3)
30 f143?: string; // Region (S3)
31 f160?: string; // Max per hour
32 f190?: number; // Transactional email flag
33 f191?: number; // Bulk email flag
34 f192?: number; // Confirm TX flag
35 f193?: number; // Save message flag
36 f280?: string; // Network password
37 f300?: number; // Used for transactions (UPLOAD)
38 f301?: number; // Used for images (UPLOAD)
39 f310?: string; // Allow list (SMS)
40 f311?: string; // Block list (SMS)
41 STAT?: any; // Status/log data
44type AccountType = "SMTP" | "POP3" | "IMAP" | "SMS" | "FTP" | "UPLOAD" | "AWSS3";
46export default function ConfigureAccountsPage() {
47 const [accounts, setAccounts] = useState<MessagingAccount[]>([]);
48 const [loading, setLoading] = useState(true);
49 const [showNewAccountModal, setShowNewAccountModal] = useState(false);
50 const [showEditModal, setShowEditModal] = useState(false);
51 const [selectedAccount, setSelectedAccount] = useState<MessagingAccount | null>(null);
52 const [newAccountType, setNewAccountType] = useState<AccountType | null>(null);
56 const interval = setInterval(loadAccounts, 30000); // Refresh every 30 seconds
57 return () => clearInterval(interval);
60 const loadAccounts = async () => {
62 const response = await fetch("/api/v1/messaging/config");
63 const data = await response.json();
65 if (data.success && data.data?.DATS) {
66 const accountsList = Array.isArray(data.data.DATS) ? data.data.DATS : [data.data.DATS];
69 const processed = accountsList.map((acc: MessagingAccount) => {
70 const status = acc.f120 || 0;
73 acc.f120s_bkcolor = "#ff8080";
76 acc.f120s_bkcolor = "#00ff00";
79 // Mark known internally
80 if (acc.f128 && acc.f128 !== 9998 && acc.f128 !== 9999) {
81 acc.f101 = "Known Internally";
87 setAccounts(processed);
90 console.error("Error loading accounts:", error);
96 const handleCreateAccount = () => {
97 setShowNewAccountModal(true);
100 const handleEditAccount = (account: MessagingAccount) => {
101 setSelectedAccount(account);
102 setShowEditModal(true);
105 const handleSetStatus = async (account: MessagingAccount, hold: boolean) => {
107 await fetch("/api/v1/messaging/config", {
109 headers: { "Content-Type": "application/json" },
110 body: JSON.stringify({
116 setTimeout(loadAccounts, 1000);
118 console.error("Error updating status:", error);
122 const getAccountInfo = (account: MessagingAccount) => {
123 const info: string[] = [];
124 if (account.f110 === "SMTP") {
125 if (account.f190 && account.f190 > 0) info.push("Txn Email");
126 if (account.f191 && account.f191 > 0) info.push("Bulk Email");
127 if (account.f192 && account.f192 > 0) info.push("Confirm TX");
128 if (account.f193 && account.f193 > 0) info.push("Save Message");
130 return info.join(", ");
134 <div className="min-h-screen bg-gray-50">
135 <div className="max-w-7xl mx-auto p-6">
137 <div className="mb-8">
138 <div className="flex items-center gap-3 mb-2">
139 <Link href="/marketing" className="text-gray-600 hover:text-gray-800">
142 <span className="text-gray-400">/</span>
143 <Link href="/marketing/messaging" className="text-gray-600 hover:text-gray-800">
146 <span className="text-gray-400">/</span>
147 <span className="text-gray-900 font-semibold">Configure Accounts</span>
149 <h1 className="text-3xl font-bold text-gray-900 mb-2">Messaging Configuration</h1>
150 <p className="text-gray-600 text-sm">
151 Manage email, SMS, and interface accounts for external communications
155 {/* New Account Button */}
156 <div className="mb-6">
158 onClick={handleCreateAccount}
159 className="px-6 py-3 bg-[#00946b] text-white rounded-lg hover:bg-[#007555] transition shadow-md flex items-center gap-2"
161 <span className="text-xl">➕</span>
166 {/* Accounts Table */}
167 <div className="bg-white rounded-lg shadow-md overflow-hidden">
168 <div className="overflow-x-auto">
170 <div className="p-8 text-center text-gray-600">Loading accounts...</div>
171 ) : accounts.length === 0 ? (
172 <div className="p-8 text-center text-gray-500">
173 No accounts configured. Click "New Account" to create one.
176 <table className="w-full text-sm">
177 <thead className="bg-gray-50 border-b border-gray-200">
179 <th className="px-4 py-3 text-left font-semibold text-gray-700">Account Name</th>
180 <th className="px-4 py-3 text-left font-semibold text-gray-700">Type</th>
181 <th className="px-4 py-3 text-left font-semibold text-gray-700">Server</th>
182 <th className="px-4 py-3 text-left font-semibold text-gray-700">Port</th>
183 <th className="px-4 py-3 text-left font-semibold text-gray-700">User</th>
184 <th className="px-4 py-3 text-left font-semibold text-gray-700">Info</th>
185 <th className="px-4 py-3 text-left font-semibold text-gray-700">Status</th>
186 <th className="px-4 py-3 text-left font-semibold text-gray-700">Actions</th>
190 {accounts.map((account, idx) => (
191 <tr key={idx} className="border-b border-gray-100 hover:bg-gray-50">
192 <td className="px-4 py-3 font-medium text-gray-900">{account.f109}</td>
193 <td className="px-4 py-3 text-gray-700">
194 <span className="px-2 py-1 bg-blue-100 text-blue-800 rounded text-xs font-medium">
198 <td className="px-4 py-3 text-gray-700 font-mono text-xs">{account.f101 || "-"}</td>
199 <td className="px-4 py-3 text-gray-700">{account.f102 || "-"}</td>
200 <td className="px-4 py-3 text-gray-700 font-mono text-xs truncate max-w-xs">
201 {account.f103 || "-"}
203 <td className="px-4 py-3 text-gray-600 text-xs">{getAccountInfo(account)}</td>
204 <td className="px-4 py-3">
206 className="px-3 py-1 rounded-full text-xs font-medium"
207 style={{ backgroundColor: account.f120s_bkcolor, color: "#000" }}
212 <td className="px-4 py-3">
213 <div className="flex gap-2">
215 onClick={() => handleEditAccount(account)}
216 className="px-3 py-1 bg-blue-600 text-white rounded text-xs hover:bg-blue-700"
220 {account.f120s === "Hold" ? (
222 onClick={() => handleSetStatus(account, false)}
223 className="px-3 py-1 bg-green-600 text-white rounded text-xs hover:bg-green-700"
229 onClick={() => handleSetStatus(account, true)}
230 className="px-3 py-1 bg-orange-600 text-white rounded text-xs hover:bg-orange-700"
245 {/* New Account Modal */}
246 {showNewAccountModal && (
248 onClose={() => setShowNewAccountModal(false)}
250 setShowNewAccountModal(false);
251 setTimeout(loadAccounts, 1000);
256 {/* Edit Account Modal */}
257 {showEditModal && selectedAccount && (
259 account={selectedAccount}
261 setShowEditModal(false);
262 setSelectedAccount(null);
265 setShowEditModal(false);
266 setSelectedAccount(null);
267 setTimeout(loadAccounts, 1000);
276// New Account Modal Component
277function NewAccountModal({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) {
278 const [accountType, setAccountType] = useState<AccountType>("SMTP");
279 const [formData, setFormData] = useState<any>({});
281 const handleSubmit = async (e: React.FormEvent) => {
285 await fetch("/api/v1/messaging/config", {
287 headers: { "Content-Type": "application/json" },
288 body: JSON.stringify({
296 console.error("Error creating account:", error);
297 alert("Failed to create account");
302 <div className="modal-overlay">
303 <div className="modal-content bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
304 <div className="sticky top-0 bg-[#5094fb] text-white p-4 rounded-t-lg flex justify-between items-center">
305 <h2 className="text-xl font-bold">Add New Messaging Account</h2>
306 <button onClick={onClose} className="text-white hover:text-gray-200 text-2xl leading-none">
311 <div className="p-6">
312 {/* Account Type Tabs */}
313 <div className="flex gap-2 mb-6 flex-wrap">
314 {(["SMTP", "IMAP", "POP3", "SMS", "FTP", "UPLOAD", "AWSS3"] as AccountType[]).map((type) => (
317 onClick={() => setAccountType(type)}
318 className={`px-4 py-2 rounded ${
320 ? "bg-[#5094fb] text-white"
321 : "bg-gray-200 text-gray-700 hover:bg-gray-300"
329 <form onSubmit={handleSubmit}>
331 accountType={accountType}
333 onChange={(data) => setFormData(data)}
337 <div className="mt-6 flex gap-3">
340 className="px-6 py-2 bg-[#00946b] text-white rounded hover:bg-[#007555]"
347 className="px-6 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400"
359// Edit Account Modal Component
360function EditAccountModal({
365 account: MessagingAccount;
367 onSuccess: () => void;
369 const [formData, setFormData] = useState<any>(account);
371 const handleSubmit = async (e: React.FormEvent) => {
375 await fetch("/api/v1/messaging/config", {
377 headers: { "Content-Type": "application/json" },
378 body: JSON.stringify(formData),
383 console.error("Error updating account:", error);
384 alert("Failed to update account");
389 <div className="modal-overlay">
390 <div className="modal-content bg-white rounded-lg shadow-xl max-w-3xl w-full max-h-[90vh] overflow-y-auto">
391 <div className="sticky top-0 bg-[#00946b] text-white p-4 rounded-t-lg flex justify-between items-center">
392 <h2 className="text-xl font-bold">{account.f109}</h2>
393 <button onClick={onClose} className="text-white hover:text-gray-200 text-2xl leading-none">
398 <div className="p-6">
399 <form onSubmit={handleSubmit}>
401 accountType={account.f110 as AccountType}
403 onChange={(data) => setFormData(data)}
407 <div className="mt-6 flex gap-3">
410 className="px-6 py-2 bg-[#00946b] text-white rounded hover:bg-[#007555]"
417 className="px-6 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400"
429// Reusable Account Form Component
430function AccountForm({
436 accountType: AccountType;
438 onChange: (data: any) => void;
441 const updateField = (field: string, value: any) => {
442 onChange({ ...formData, [field]: value });
446 <div className="space-y-4">
448 <label className="block text-sm font-medium text-gray-700 mb-1">Account Name</label>
451 value={formData.f109 || ""}
452 onChange={(e) => updateField("f109", e.target.value)}
453 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b] focus:border-transparent"
458 {(accountType === "SMTP" || accountType === "POP3" || accountType === "IMAP") && (
460 {(accountType === "SMTP" || accountType === "POP3") && (
462 <label className="block text-sm font-medium text-gray-700 mb-1">Well Known Server</label>
464 value={formData.f128 || ""}
465 onChange={(e) => updateField("f128", e.target.value)}
466 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
468 <option value="">Custom</option>
469 <option value="1">Microsoft Office 365</option>
470 <option value="2">Outlook.com</option>
471 <option value="9998">Microsoft Exchange Online</option>
472 <option value="12003">iServe.net.nz</option>
473 <option value="9999">Corporate Connector</option>
478 <div className="grid grid-cols-2 gap-4">
480 <label className="block text-sm font-medium text-gray-700 mb-1">Server</label>
483 value={formData.f101 || ""}
484 onChange={(e) => updateField("f101", e.target.value)}
485 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
489 <label className="block text-sm font-medium text-gray-700 mb-1">Port</label>
492 value={formData.f102 || ""}
493 onChange={(e) => updateField("f102", e.target.value)}
494 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
500 <label className="block text-sm font-medium text-gray-700 mb-1">User/Email</label>
503 value={formData.f103 || ""}
504 onChange={(e) => updateField("f103", e.target.value)}
505 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
510 <label className="block text-sm font-medium text-gray-700 mb-1">Password</label>
513 value={formData.f104 || ""}
514 onChange={(e) => updateField("f104", e.target.value)}
515 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
519 {accountType === "SMTP" && (
522 <label className="block text-sm font-medium text-gray-700 mb-1">From Name</label>
525 value={formData.f112 || ""}
526 onChange={(e) => updateField("f112", e.target.value)}
527 placeholder="My Store <email@example.com>"
528 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
533 <label className="block text-sm font-medium text-gray-700 mb-1">Reply To</label>
536 value={formData.f118 || ""}
537 onChange={(e) => updateField("f118", e.target.value)}
538 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
542 <div className="space-y-2">
543 <label className="flex items-center gap-2">
546 checked={formData.f190 !== undefined ? formData.f190 > 0 : true}
547 onChange={(e) => updateField("f190", e.target.checked ? 1 : 0)}
550 <span className="text-sm text-gray-700">Transactional emails (Receipts, POs, etc.)</span>
552 <label className="flex items-center gap-2">
555 checked={formData.f191 > 0}
556 onChange={(e) => updateField("f191", e.target.checked ? 1 : 0)}
559 <span className="text-sm text-gray-700">Bulk marketing emails</span>
561 <label className="flex items-center gap-2">
564 checked={formData.f192 > 0}
565 onChange={(e) => updateField("f192", e.target.checked ? 1 : 0)}
568 <span className="text-sm text-gray-700">Send BCC to Fieldpine for confirmation</span>
570 <label className="flex items-center gap-2">
573 checked={formData.f193 > 0}
574 onChange={(e) => updateField("f193", e.target.checked ? 1 : 0)}
577 <span className="text-sm text-gray-700">Save sent email body</span>
585 {accountType === "SMS" && (
588 <label className="block text-sm font-medium text-gray-700 mb-1">SMS Provider</label>
590 value={formData.f130 || ""}
591 onChange={(e) => updateField("f130", e.target.value)}
592 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
594 <option value="">--Select One--</option>
595 <option value="2">Fieldpine Gateway</option>
596 <option value="1">Email Gateway</option>
597 <option value="3">Output to File</option>
598 <option value="4">Burst SMS</option>
599 <option value="6401">NZ. Spark eTxt</option>
603 {formData.f130 === "4" && (
606 <label className="block text-sm font-medium text-gray-700 mb-1">API Key</label>
609 value={formData.f133 || ""}
610 onChange={(e) => updateField("f133", e.target.value)}
611 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
615 <label className="block text-sm font-medium text-gray-700 mb-1">API Secret</label>
618 value={formData.f134 || ""}
619 onChange={(e) => updateField("f134", e.target.value)}
620 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
628 {accountType === "FTP" && (
630 <div className="grid grid-cols-2 gap-4">
632 <label className="block text-sm font-medium text-gray-700 mb-1">Server</label>
635 value={formData.f101 || ""}
636 onChange={(e) => updateField("f101", e.target.value)}
637 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
641 <label className="block text-sm font-medium text-gray-700 mb-1">Port</label>
644 value={formData.f102 || ""}
645 onChange={(e) => updateField("f102", e.target.value)}
646 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
650 <div className="grid grid-cols-2 gap-4">
652 <label className="block text-sm font-medium text-gray-700 mb-1">Username</label>
655 value={formData.f103 || ""}
656 onChange={(e) => updateField("f103", e.target.value)}
657 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
661 <label className="block text-sm font-medium text-gray-700 mb-1">Password</label>
664 value={formData.f104 || ""}
665 onChange={(e) => updateField("f104", e.target.value)}
666 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
673 {accountType === "UPLOAD" && (
676 <label className="block text-sm font-medium text-gray-700 mb-1">Local Directory</label>
679 value={formData.f101 || ""}
680 onChange={(e) => updateField("f101", e.target.value)}
681 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
684 <div className="space-y-2">
685 <label className="flex items-center gap-2">
688 checked={formData.f300 > 0}
689 onChange={(e) => updateField("f300", e.target.checked ? 1 : 0)}
692 <span className="text-sm text-gray-700">Used for transactions</span>
694 <label className="flex items-center gap-2">
697 checked={formData.f301 > 0}
698 onChange={(e) => updateField("f301", e.target.checked ? 1 : 0)}
701 <span className="text-sm text-gray-700">Used for images</span>
707 {accountType === "AWSS3" && (
710 <label className="block text-sm font-medium text-gray-700 mb-1">Bucket Name</label>
713 value={formData.f140 || ""}
714 onChange={(e) => updateField("f140", e.target.value)}
715 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
719 <label className="block text-sm font-medium text-gray-700 mb-1">Access Key</label>
722 value={formData.f141 || ""}
723 onChange={(e) => updateField("f141", e.target.value)}
724 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
728 <label className="block text-sm font-medium text-gray-700 mb-1">Secret Key</label>
731 value={formData.f142 || ""}
732 onChange={(e) => updateField("f142", e.target.value)}
733 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"
737 <label className="block text-sm font-medium text-gray-700 mb-1">Region</label>
740 value={formData.f143 || ""}
741 onChange={(e) => updateField("f143", e.target.value)}
742 className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-[#00946b]"