Checkpoint: Ajout de la possibilité d'envoyer des emails via SMTP directement (sans Resend) :
**Modifications de la base de données** : - Ajout des champs SMTP dans la table `emailConfig` : smtpHost, smtpPort, smtpSecure, smtpUser, smtpPassword **Backend** : - Installation de `nodemailer` pour l'envoi SMTP - Création de `server/_core/smtpSender.ts` avec les fonctions d'envoi SMTP et de test de connexion - Modification de `server/_core/emailSender.ts` pour supporter les deux méthodes (Resend et SMTP) - Priorité donnée à SMTP si configuré, sinon fallback sur Resend **Frontend** : - Mise à jour de `AdminEmailConfig.tsx` pour permettre le choix entre Resend et SMTP - Ajout d'un sélecteur de méthode d'envoi (Resend API / SMTP Direct) - Formulaire de configuration SMTP avec : * Hôte SMTP (ex: smtp.gmail.com) * Port (587, 465, 25) * Sécurité (TLS, SSL, None) * Identifiants (utilisateur/mot de passe) * Email expéditeur **Compatibilité** : - Support des principaux fournisseurs SMTP (Gmail, Outlook, serveurs SMTP personnalisés) - Instructions pour les mots de passe d'application Gmail - Mode simulation toujours disponible pour les tests
This commit is contained in:
@@ -8,7 +8,9 @@ import { Button } from "@/components/ui/button";
|
||||
import { toast } from "sonner";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { AlertCircle, CheckCircle2, Mail, Send } from "lucide-react";
|
||||
import { AlertCircle, CheckCircle2, Mail, Send, Server } from "lucide-react";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
|
||||
export default function AdminEmailConfig() {
|
||||
const { data: config, isLoading, refetch } = trpc.emailConfig.get.useQuery();
|
||||
@@ -16,12 +18,17 @@ export default function AdminEmailConfig() {
|
||||
const testEmailMutation = trpc.emailConfig.testEmail.useMutation();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
provider: "resend",
|
||||
provider: "resend" as "resend" | "smtp",
|
||||
apiKey: "",
|
||||
fromEmail: "",
|
||||
fromName: "Formation Manager Itinova",
|
||||
mode: "simulation" as "simulation" | "production",
|
||||
domainVerified: false,
|
||||
smtpHost: "",
|
||||
smtpPort: 587,
|
||||
smtpSecure: "tls" as "none" | "tls" | "ssl",
|
||||
smtpUser: "",
|
||||
smtpPassword: "",
|
||||
});
|
||||
|
||||
const [testEmail, setTestEmail] = useState("");
|
||||
@@ -30,12 +37,17 @@ export default function AdminEmailConfig() {
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
setFormData({
|
||||
provider: config.provider,
|
||||
provider: config.provider as "resend" | "smtp",
|
||||
apiKey: config.apiKey || "",
|
||||
fromEmail: config.fromEmail,
|
||||
fromName: config.fromName,
|
||||
mode: config.mode,
|
||||
domainVerified: config.domainVerified,
|
||||
smtpHost: config.smtpHost || "",
|
||||
smtpPort: config.smtpPort || 587,
|
||||
smtpSecure: (config.smtpSecure as "none" | "tls" | "ssl") || "tls",
|
||||
smtpUser: config.smtpUser || "",
|
||||
smtpPassword: config.smtpPassword || "",
|
||||
});
|
||||
}
|
||||
}, [config]);
|
||||
@@ -150,49 +162,179 @@ export default function AdminEmailConfig() {
|
||||
{/* Formulaire de configuration */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Configuration Resend</CardTitle>
|
||||
<CardTitle>Configuration de l'envoi d'emails</CardTitle>
|
||||
<CardDescription>
|
||||
Configurez votre compte Resend pour envoyer de vrais emails
|
||||
Choisissez votre méthode d'envoi : Resend (API) ou SMTP direct
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Sélecteur de provider */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="apiKey">Clé API Resend *</Label>
|
||||
<Input
|
||||
id="apiKey"
|
||||
type="password"
|
||||
placeholder="re_..."
|
||||
value={formData.apiKey}
|
||||
onChange={(e) => setFormData({ ...formData, apiKey: e.target.value })}
|
||||
/>
|
||||
<Label htmlFor="provider">Méthode d'envoi</Label>
|
||||
<Select
|
||||
value={formData.provider}
|
||||
onValueChange={(value: "resend" | "smtp") => setFormData({ ...formData, provider: value })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="resend">
|
||||
<div className="flex items-center gap-2">
|
||||
<Mail className="h-4 w-4" />
|
||||
Resend (API)
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="smtp">
|
||||
<div className="flex items-center gap-2">
|
||||
<Server className="h-4 w-4" />
|
||||
SMTP Direct
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Obtenez votre clé API sur{" "}
|
||||
<a
|
||||
href="https://resend.com/api-keys"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
resend.com/api-keys
|
||||
</a>
|
||||
{formData.provider === "resend"
|
||||
? "Service d'envoi d'emails via API (recommandé)"
|
||||
: "Connexion directe à un serveur SMTP (Gmail, Outlook, etc.)"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="fromEmail">Email expéditeur *</Label>
|
||||
<Input
|
||||
id="fromEmail"
|
||||
type="email"
|
||||
placeholder="noreply@votredomaine.com"
|
||||
value={formData.fromEmail}
|
||||
onChange={(e) => setFormData({ ...formData, fromEmail: e.target.value })}
|
||||
required
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
L'adresse email doit être vérifiée dans votre compte Resend
|
||||
</p>
|
||||
</div>
|
||||
{/* Configuration Resend */}
|
||||
{formData.provider === "resend" && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="apiKey">Clé API Resend *</Label>
|
||||
<Input
|
||||
id="apiKey"
|
||||
type="password"
|
||||
placeholder="re_..."
|
||||
value={formData.apiKey}
|
||||
onChange={(e) => setFormData({ ...formData, apiKey: e.target.value })}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Obtenez votre clé API sur{" "}
|
||||
<a
|
||||
href="https://resend.com/api-keys"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
resend.com/api-keys
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="fromEmail">Email expéditeur *</Label>
|
||||
<Input
|
||||
id="fromEmail"
|
||||
type="email"
|
||||
placeholder="noreply@votredomaine.com"
|
||||
value={formData.fromEmail}
|
||||
onChange={(e) => setFormData({ ...formData, fromEmail: e.target.value })}
|
||||
required
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
L'adresse email doit être vérifiée dans votre compte Resend
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Configuration SMTP */}
|
||||
{formData.provider === "smtp" && (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="smtpHost">Hôte SMTP *</Label>
|
||||
<Input
|
||||
id="smtpHost"
|
||||
type="text"
|
||||
placeholder="smtp.gmail.com"
|
||||
value={formData.smtpHost}
|
||||
onChange={(e) => setFormData({ ...formData, smtpHost: e.target.value })}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="smtpPort">Port *</Label>
|
||||
<Input
|
||||
id="smtpPort"
|
||||
type="number"
|
||||
placeholder="587"
|
||||
value={formData.smtpPort}
|
||||
onChange={(e) => setFormData({ ...formData, smtpPort: parseInt(e.target.value) || 587 })}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="smtpSecure">Sécurité</Label>
|
||||
<Select
|
||||
value={formData.smtpSecure}
|
||||
onValueChange={(value: "none" | "tls" | "ssl") => setFormData({ ...formData, smtpSecure: value })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="tls">TLS/STARTTLS (port 587)</SelectItem>
|
||||
<SelectItem value="ssl">SSL (port 465)</SelectItem>
|
||||
<SelectItem value="none">Aucune (port 25)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="smtpUser">Nom d'utilisateur *</Label>
|
||||
<Input
|
||||
id="smtpUser"
|
||||
type="text"
|
||||
placeholder="votre@email.com"
|
||||
value={formData.smtpUser}
|
||||
onChange={(e) => setFormData({ ...formData, smtpUser: e.target.value })}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="smtpPassword">Mot de passe *</Label>
|
||||
<Input
|
||||
id="smtpPassword"
|
||||
type="password"
|
||||
placeholder="Mot de passe ou mot de passe d'application"
|
||||
value={formData.smtpPassword}
|
||||
onChange={(e) => setFormData({ ...formData, smtpPassword: e.target.value })}
|
||||
required
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Pour Gmail, utilisez un{" "}
|
||||
<a
|
||||
href="https://support.google.com/accounts/answer/185833"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
mot de passe d'application
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="fromEmail">Email expéditeur *</Label>
|
||||
<Input
|
||||
id="fromEmail"
|
||||
type="email"
|
||||
placeholder="noreply@votredomaine.com"
|
||||
value={formData.fromEmail}
|
||||
onChange={(e) => setFormData({ ...formData, fromEmail: e.target.value })}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="fromName">Nom de l'expéditeur *</Label>
|
||||
|
||||
Reference in New Issue
Block a user