264 lines
9.3 KiB
TypeScript
264 lines
9.3 KiB
TypeScript
import { json, type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/node";
|
|
import { Form, useActionData, useLoaderData } from "@remix-run/react";
|
|
import { requireAuthLevel } from "~/utils/auth.server";
|
|
import { testEmailConnection } from "~/utils/mail.server";
|
|
import DashboardLayout from "~/components/DashboardLayout";
|
|
import { useState } from "react";
|
|
import { prisma } from "~/utils/db.server";
|
|
|
|
export async function loader({ request }: LoaderFunctionArgs) {
|
|
// Require auth level 3 to access mail settings
|
|
const user = await requireAuthLevel(request, 3);
|
|
|
|
const mailSettings = await prisma.mailSettings.findFirst();
|
|
|
|
return json({ mailSettings, user });
|
|
}
|
|
|
|
export async function action({ request }: ActionFunctionArgs) {
|
|
// Require auth level 3 to modify mail settings
|
|
await requireAuthLevel(request, 3);
|
|
|
|
const formData = await request.formData();
|
|
const action = formData.get("_action");
|
|
|
|
if (action === "test") {
|
|
const result = await testEmailConnection();
|
|
return json(result);
|
|
}
|
|
|
|
const host = formData.get("host") as string;
|
|
const port = parseInt(formData.get("port") as string);
|
|
const secure = formData.get("secure") === "on";
|
|
const username = formData.get("username") as string;
|
|
const password = formData.get("password") as string;
|
|
const fromName = formData.get("fromName") as string;
|
|
const fromEmail = formData.get("fromEmail") as string;
|
|
|
|
if (!host || !port || !username || !password || !fromName || !fromEmail) {
|
|
return json({ error: "All fields are required" }, { status: 400 });
|
|
}
|
|
|
|
try {
|
|
// Check if settings exist
|
|
const existingSettings = await prisma.mailSettings.findFirst();
|
|
|
|
if (existingSettings) {
|
|
// Update existing settings
|
|
await prisma.mailSettings.update({
|
|
where: { id: existingSettings.id },
|
|
data: {
|
|
host,
|
|
port,
|
|
secure,
|
|
username,
|
|
password,
|
|
fromName,
|
|
fromEmail,
|
|
},
|
|
});
|
|
} else {
|
|
// Create new settings
|
|
await prisma.mailSettings.create({
|
|
data: {
|
|
host,
|
|
port,
|
|
secure,
|
|
username,
|
|
password,
|
|
fromName,
|
|
fromEmail,
|
|
},
|
|
});
|
|
}
|
|
|
|
return json({ success: "Mail settings saved successfully" });
|
|
} catch (error) {
|
|
return json({ error: "Failed to save mail settings" }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
export default function MailSettings() {
|
|
const { mailSettings, user } = useLoaderData<typeof loader>();
|
|
const actionData = useActionData<typeof action>();
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
|
|
return (
|
|
<DashboardLayout user={user}>
|
|
<div className="max-w-2xl mx-auto p-6">
|
|
<h1 className="text-2xl font-bold mb-6">Mail Settings</h1>
|
|
|
|
{/* SMTP Configuration Examples */}
|
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
|
<h3 className="text-lg font-semibold text-blue-800 mb-2">Common SMTP Settings</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
|
<div>
|
|
<h4 className="font-medium text-blue-700">Gmail</h4>
|
|
<p>Host: smtp.gmail.com</p>
|
|
<p>Port: 587 (TLS) or 465 (SSL)</p>
|
|
<p>Note: Use App Password, not regular password</p>
|
|
</div>
|
|
<div>
|
|
<h4 className="font-medium text-blue-700">Outlook/Hotmail</h4>
|
|
<p>Host: smtp-mail.outlook.com</p>
|
|
<p>Port: 587 (TLS)</p>
|
|
<p>Secure: No (uses STARTTLS)</p>
|
|
</div>
|
|
<div>
|
|
<h4 className="font-medium text-blue-700">Yahoo</h4>
|
|
<p>Host: smtp.mail.yahoo.com</p>
|
|
<p>Port: 587 (TLS) or 465 (SSL)</p>
|
|
<p>Note: Enable "Less secure apps"</p>
|
|
</div>
|
|
<div>
|
|
<h4 className="font-medium text-blue-700">Custom SMTP</h4>
|
|
<p>Contact your email provider</p>
|
|
<p>Port 587 (TLS) is most common</p>
|
|
<p>Port 465 (SSL) for secure connections</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{actionData?.error && (
|
|
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
|
{actionData.error}
|
|
</div>
|
|
)}
|
|
|
|
{actionData?.success && (
|
|
<div className="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
|
{actionData.success}
|
|
</div>
|
|
)}
|
|
|
|
<Form method="post" className="space-y-4">
|
|
<div>
|
|
<label htmlFor="host" className="block text-sm font-medium text-gray-700">
|
|
SMTP Host
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="host"
|
|
name="host"
|
|
defaultValue={mailSettings?.host || ""}
|
|
className="mt-1 p-2 h-9 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
|
placeholder="smtp.gmail.com"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="port" className="block text-sm font-medium text-gray-700">
|
|
SMTP Port
|
|
</label>
|
|
<input
|
|
type="number"
|
|
id="port"
|
|
name="port"
|
|
defaultValue={mailSettings?.port || 587}
|
|
className="mt-1 block p-2 h-9 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
name="secure"
|
|
defaultChecked={mailSettings?.secure || false}
|
|
className="rounded p-2 h-9 border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-700">Use SSL/TLS</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
|
|
Username
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="username"
|
|
name="username"
|
|
defaultValue={mailSettings?.username || ""}
|
|
className="mt-1 block p-2 h-9 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
|
|
Password
|
|
</label>
|
|
<div className="relative">
|
|
<input
|
|
type={showPassword ? "text" : "password"}
|
|
id="password"
|
|
name="password"
|
|
defaultValue={mailSettings?.password || ""}
|
|
className="mt-1 block w-full p-2 h-9 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 pr-10"
|
|
required
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
className="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5"
|
|
>
|
|
{showPassword ? "Hide" : "Show"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="fromName" className="block text-sm font-medium text-gray-700">
|
|
From Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="fromName"
|
|
name="fromName"
|
|
defaultValue={mailSettings?.fromName || ""}
|
|
className="mt-1 block w-full p-2 h-9 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
|
placeholder="Your Company Name"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="fromEmail" className="block text-sm font-medium text-gray-700">
|
|
From Email
|
|
</label>
|
|
<input
|
|
type="email"
|
|
id="fromEmail"
|
|
name="fromEmail"
|
|
defaultValue={mailSettings?.fromEmail || ""}
|
|
className="mt-1 block p-2 h-9 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
|
placeholder="noreply@yourcompany.com"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex space-x-4">
|
|
<button
|
|
type="submit"
|
|
className="flex-1 flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
>
|
|
Save Mail Settings
|
|
</button>
|
|
|
|
<button
|
|
type="submit"
|
|
name="_action"
|
|
value="test"
|
|
className="flex-1 flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
>
|
|
Test Connection
|
|
</button>
|
|
</div>
|
|
</Form>
|
|
</div>
|
|
</DashboardLayout>
|
|
);
|
|
} |