feat: complete i18n support, telegram webhook, and security improvements
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { useState, useEffect, useTransition, useCallback } from "react";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { formatBytes } from "@/lib/format";
|
||||
import { useDictionary } from "@/components/DictionaryContext";
|
||||
|
||||
interface Mailbox {
|
||||
username: string;
|
||||
@@ -32,6 +33,7 @@ export default function MailboxesPage() {
|
||||
const [createForm, setCreateForm] = useState({ local_part: "", name: "", password: "", quota: 3072 });
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [formError, setFormError] = useState("");
|
||||
const dict = useDictionary();
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/domains")
|
||||
@@ -81,7 +83,7 @@ export default function MailboxesPage() {
|
||||
};
|
||||
|
||||
const handleDelete = (username: string) => {
|
||||
if (!confirm(`"${username}" hesabını silmek istediğinizden emin misiniz?`)) return;
|
||||
if (!confirm(`"${username}" ${dict.mailboxes.deleteConfirm || "hesabını silmek istediğinizden emin misiniz?"}`)) return;
|
||||
startTransition(async () => {
|
||||
await fetch(`/api/mailboxes/${encodeURIComponent(username)}`, { method: "DELETE" });
|
||||
setMailboxes((prev) => prev.filter((m) => m.username !== username));
|
||||
@@ -128,11 +130,11 @@ export default function MailboxesPage() {
|
||||
<>
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<h1 className="page-title">Mail Hesapları</h1>
|
||||
<h1 className="page-title">{dict.mailboxes.title || "Mail Hesapları"}</h1>
|
||||
<p className="page-subtitle">
|
||||
{selectedDomain
|
||||
? `${selectedDomain} — ${mailboxes.length} hesap`
|
||||
: "Domain seçin"}
|
||||
? `${selectedDomain} — ${mailboxes.length} ${dict.mailboxes.accounts || "hesap"}`
|
||||
: (dict.mailboxes.selectDomain || "Domain seçin")}
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 10, alignItems: "center" }}>
|
||||
@@ -160,7 +162,7 @@ export default function MailboxesPage() {
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
disabled={!selectedDomain}
|
||||
>
|
||||
<PlusIcon /> Hesap Ekle
|
||||
<PlusIcon /> {dict.mailboxes.addAccount || "Hesap Ekle"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -172,7 +174,7 @@ export default function MailboxesPage() {
|
||||
<input
|
||||
type="text"
|
||||
className="input search-input"
|
||||
placeholder="E-posta veya isim ara..."
|
||||
placeholder={dict.mailboxes.searchPlaceholder || "E-posta veya isim ara..."}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
@@ -188,21 +190,21 @@ export default function MailboxesPage() {
|
||||
<div className="empty-state">
|
||||
<div className="empty-icon"><MailIcon size={24} /></div>
|
||||
<div style={{ fontWeight: 600 }}>
|
||||
{selectedDomain ? "Bu domainde mail hesabı yok" : "Domain seçin"}
|
||||
{selectedDomain ? (dict.mailboxes.noMailboxes || "Bu domainde mail hesabı yok") : (dict.mailboxes.selectDomain || "Domain seçin")}
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: "var(--text-secondary)" }}>
|
||||
{selectedDomain ? '"Hesap Ekle" butonuna tıklayın' : "Sol üstteki listeden domain seçin"}
|
||||
{selectedDomain ? (dict.mailboxes.noMailboxesDesc || "\"Hesap Ekle\" butonuna tıklayın") : (dict.mailboxes.selectDomainDesc || "Sol üstteki listeden domain seçin")}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>E-posta</th>
|
||||
<th>Ad Soyad</th>
|
||||
<th>Kota</th>
|
||||
<th>Durum</th>
|
||||
<th>İşlemler</th>
|
||||
<th>{dict.mailboxes.email || "E-posta"}</th>
|
||||
<th>{dict.mailboxes.name || "Ad Soyad"}</th>
|
||||
<th>{dict.mailboxes.quota || "Kota"}</th>
|
||||
<th>{dict.mailboxes.status || "Durum"}</th>
|
||||
<th>{dict.mailboxes.actions || "İşlemler"}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -237,7 +239,7 @@ export default function MailboxesPage() {
|
||||
</td>
|
||||
<td>
|
||||
<span className={`badge ${String(m.active) === "1" ? "badge-green" : "badge-red"}`}>
|
||||
{String(m.active) === "1" ? "● Aktif" : "● Pasif"}
|
||||
{String(m.active) === "1" ? `● ${dict.mailboxes.active || "Aktif"}` : `● ${dict.mailboxes.inactive || "Pasif"}`}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@@ -245,28 +247,28 @@ export default function MailboxesPage() {
|
||||
<button
|
||||
className="btn btn-ghost btn-sm"
|
||||
onClick={() => setShowInfoModal(m.username)}
|
||||
title="Bağlantı Bilgileri"
|
||||
title={dict.mailboxes.info || "Bağlantı Bilgileri"}
|
||||
>
|
||||
<InfoIcon />
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-ghost btn-sm"
|
||||
onClick={() => setShowPasswordModal(m.username)}
|
||||
title="Şifre Değiştir"
|
||||
title={dict.mailboxes.changePassword || "Şifre Değiştir"}
|
||||
>
|
||||
<KeyIcon />
|
||||
</button>
|
||||
<button
|
||||
className={`btn btn-sm ${String(m.active) === "1" ? "btn-ghost" : "btn-success"}`}
|
||||
onClick={() => handleToggle(m.username, m.active)}
|
||||
title={String(m.active) === "1" ? "Pasife Al" : "Aktif Et"}
|
||||
title={String(m.active) === "1" ? (dict.mailboxes.deactivate || "Pasife Al") : (dict.mailboxes.activate || "Aktif Et")}
|
||||
>
|
||||
{String(m.active) === "1" ? <PauseIcon /> : <PlayIcon />}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-danger btn-sm"
|
||||
onClick={() => handleDelete(m.username)}
|
||||
title="Sil"
|
||||
title={dict.mailboxes.delete || "Sil"}
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
@@ -286,14 +288,14 @@ export default function MailboxesPage() {
|
||||
<div className="modal-overlay" onClick={(e) => e.target === e.currentTarget && setShowCreateModal(false)}>
|
||||
<div className="modal">
|
||||
<div className="modal-header">
|
||||
<h2 className="modal-title">Yeni Mail Hesabı</h2>
|
||||
<h2 className="modal-title">{dict.mailboxes.newAccount || "Yeni Mail Hesabı"}</h2>
|
||||
<button className="modal-close" onClick={() => setShowCreateModal(false)}><XIcon /></button>
|
||||
</div>
|
||||
<form onSubmit={handleCreate}>
|
||||
<div className="modal-body form-group">
|
||||
{formError && <div className="error-msg">{formError}</div>}
|
||||
<div>
|
||||
<label className="label">Kullanıcı Adı</label>
|
||||
<label className="label">{dict.mailboxes.username || "Kullanıcı Adı"}</label>
|
||||
<div style={{ display: "flex" }}>
|
||||
<input
|
||||
type="text" className="input"
|
||||
@@ -313,29 +315,29 @@ export default function MailboxesPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Ad Soyad</label>
|
||||
<label className="label">{dict.mailboxes.name || "Ad Soyad"}</label>
|
||||
<input type="text" className="input" placeholder="Emina Karabudak"
|
||||
value={createForm.name}
|
||||
onChange={(e) => setCreateForm({ ...createForm, name: e.target.value })}
|
||||
required />
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Şifre</label>
|
||||
<input type="password" className="input" placeholder="Güçlü bir şifre"
|
||||
<label className="label">{dict.mailboxes.password || "Şifre"}</label>
|
||||
<input type="password" className="input" placeholder="********"
|
||||
value={createForm.password}
|
||||
onChange={(e) => setCreateForm({ ...createForm, password: e.target.value })}
|
||||
required />
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Kota (MB)</label>
|
||||
<label className="label">{dict.mailboxes.quotaMb || "Kota (MB)"}</label>
|
||||
<input type="number" className="input" value={createForm.quota} min={100} max={102400}
|
||||
onChange={(e) => setCreateForm({ ...createForm, quota: parseInt(e.target.value) || 3072 })} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-ghost" onClick={() => setShowCreateModal(false)}>İptal</button>
|
||||
<button type="button" className="btn btn-ghost" onClick={() => setShowCreateModal(false)}>{dict.mailboxes.cancel || "İptal"}</button>
|
||||
<button type="submit" className="btn btn-primary" disabled={isPending}>
|
||||
{isPending ? <span className="spinner" /> : <PlusIcon />} Oluştur
|
||||
{isPending ? <span className="spinner" /> : <PlusIcon />} {dict.mailboxes.create || "Oluştur"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -348,26 +350,26 @@ export default function MailboxesPage() {
|
||||
<div className="modal-overlay" onClick={(e) => e.target === e.currentTarget && setShowPasswordModal(null)}>
|
||||
<div className="modal">
|
||||
<div className="modal-header">
|
||||
<h2 className="modal-title">Şifre Değiştir</h2>
|
||||
<h2 className="modal-title">{dict.mailboxes.changePassword || "Şifre Değiştir"}</h2>
|
||||
<button className="modal-close" onClick={() => setShowPasswordModal(null)}><XIcon /></button>
|
||||
</div>
|
||||
<form onSubmit={handlePasswordChange}>
|
||||
<div className="modal-body form-group">
|
||||
<div style={{ fontSize: 13, color: "var(--text-secondary)" }}>
|
||||
<strong style={{ color: "var(--text-primary)" }}>{showPasswordModal}</strong> için yeni şifre
|
||||
<strong style={{ color: "var(--text-primary)" }}>{showPasswordModal}</strong> {dict.mailboxes.newPasswordFor || "için yeni şifre"}
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Yeni Şifre</label>
|
||||
<input type="password" className="input" placeholder="Yeni şifre"
|
||||
<label className="label">{dict.mailboxes.password || "Yeni Şifre"}</label>
|
||||
<input type="password" className="input" placeholder="********"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
required autoFocus />
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-ghost" onClick={() => setShowPasswordModal(null)}>İptal</button>
|
||||
<button type="button" className="btn btn-ghost" onClick={() => setShowPasswordModal(null)}>{dict.mailboxes.cancel || "İptal"}</button>
|
||||
<button type="submit" className="btn btn-primary" disabled={isPending}>
|
||||
{isPending ? <span className="spinner" /> : <KeyIcon />} Güncelle
|
||||
{isPending ? <span className="spinner" /> : <KeyIcon />} {dict.mailboxes.update || "Güncelle"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -379,49 +381,49 @@ export default function MailboxesPage() {
|
||||
<div className="modal-overlay" onClick={(e) => e.target === e.currentTarget && setShowInfoModal(null)}>
|
||||
<div className="modal">
|
||||
<div className="modal-header">
|
||||
<h2 className="modal-title">İstemci Bağlantı Bilgileri</h2>
|
||||
<h2 className="modal-title">{dict.mailboxes.connectionInfo || "İstemci Bağlantı Bilgileri"}</h2>
|
||||
<button className="modal-close" onClick={() => setShowInfoModal(null)}><XIcon /></button>
|
||||
</div>
|
||||
<div className="modal-body form-group">
|
||||
<div style={{ fontSize: 13, color: "var(--text-secondary)", marginBottom: 10 }}>
|
||||
<strong>{showInfoModal}</strong> hesabını Apple Mail, Outlook veya telefonunuza kurmak için aşağıdaki bilgileri kullanın:
|
||||
<strong>{showInfoModal}</strong> {dict.mailboxes.connectionInfoDesc || "hesabını kurmak için aşağıdaki bilgileri kullanın:"}
|
||||
</div>
|
||||
|
||||
<div style={{ background: "var(--bg-hover)", padding: 12, borderRadius: "var(--radius)", border: "1px solid var(--border)", fontSize: 13 }}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<div style={{ color: "var(--text-secondary)", fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5 }}>IMAP (Gelen Sunucu)</div>
|
||||
<div style={{ color: "var(--text-secondary)", fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5 }}>{dict.mailboxes.imap || "IMAP (Gelen Sunucu)"}</div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", marginTop: 4, alignItems: "center" }}>
|
||||
<span>Sunucu: <strong>mail.ayris.tech</strong></span>
|
||||
<span>{dict.mailboxes.server || "Sunucu"}: <strong>mail.ayris.tech</strong></span>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<button className="btn btn-ghost btn-sm" style={{ padding: "4px 8px", fontSize: 11 }} onClick={() => navigator.clipboard.writeText("mail.ayris.tech")} title="Kopyala"><CopyIcon /> Kopyala</button>
|
||||
<button className="btn btn-ghost btn-sm" style={{ padding: "4px 8px", fontSize: 11 }} onClick={() => navigator.clipboard.writeText("mail.ayris.tech")} title="Kopyala"><CopyIcon /> {dict.mailboxes.copy || "Kopyala"}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 2 }}>Port: <strong>993</strong> (SSL/TLS)</div>
|
||||
<div style={{ marginTop: 2 }}>{dict.mailboxes.port || "Port"}: <strong>993</strong> (SSL/TLS)</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 12, paddingTop: 12, borderTop: "1px solid var(--border)" }}>
|
||||
<div style={{ color: "var(--text-secondary)", fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5 }}>SMTP (Giden Sunucu)</div>
|
||||
<div style={{ color: "var(--text-secondary)", fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5 }}>{dict.mailboxes.smtp || "SMTP (Giden Sunucu)"}</div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", marginTop: 4, alignItems: "center" }}>
|
||||
<span>Sunucu: <strong>mail.ayris.tech</strong></span>
|
||||
<span>{dict.mailboxes.server || "Sunucu"}: <strong>mail.ayris.tech</strong></span>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<button className="btn btn-ghost btn-sm" style={{ padding: "4px 8px", fontSize: 11 }} onClick={() => navigator.clipboard.writeText("mail.ayris.tech")} title="Kopyala"><CopyIcon /> Kopyala</button>
|
||||
<button className="btn btn-ghost btn-sm" style={{ padding: "4px 8px", fontSize: 11 }} onClick={() => navigator.clipboard.writeText("mail.ayris.tech")} title="Kopyala"><CopyIcon /> {dict.mailboxes.copy || "Kopyala"}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 2 }}>Port: <strong>587</strong> (STARTTLS) <span style={{ color: "var(--text-secondary)" }}>veya 465 (SSL)</span></div>
|
||||
<div style={{ marginTop: 2 }}>{dict.mailboxes.port || "Port"}: <strong>587</strong> (STARTTLS) <span style={{ color: "var(--text-secondary)" }}>veya 465 (SSL)</span></div>
|
||||
</div>
|
||||
|
||||
<div style={{ paddingTop: 12, borderTop: "1px solid var(--border)" }}>
|
||||
<div style={{ color: "var(--text-secondary)", fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5 }}>Kimlik Doğrulama</div>
|
||||
<div style={{ color: "var(--text-secondary)", fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5 }}>{dict.mailboxes.auth || "Kimlik Doğrulama"}</div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", marginTop: 4, alignItems: "center" }}>
|
||||
<span>Kullanıcı Adı: <strong>{showInfoModal}</strong></span>
|
||||
<button className="btn btn-ghost btn-sm" style={{ padding: "4px 8px", fontSize: 11 }} onClick={() => navigator.clipboard.writeText(showInfoModal)} title="Kopyala"><CopyIcon /> Kopyala</button>
|
||||
<span>{dict.mailboxes.username || "Kullanıcı Adı"}: <strong>{showInfoModal}</strong></span>
|
||||
<button className="btn btn-ghost btn-sm" style={{ padding: "4px 8px", fontSize: 11 }} onClick={() => navigator.clipboard.writeText(showInfoModal)} title="Kopyala"><CopyIcon /> {dict.mailboxes.copy || "Kopyala"}</button>
|
||||
</div>
|
||||
<div style={{ color: "var(--text-secondary)", marginTop: 4 }}>Şifre: <span style={{ fontStyle: "italic" }}>Hesap oluştururken belirlediğiniz şifre</span></div>
|
||||
<div style={{ color: "var(--text-secondary)", marginTop: 4 }}>{dict.mailboxes.password || "Şifre"}: <span style={{ fontStyle: "italic" }}>{dict.mailboxes.authPassword || "Hesap oluştururken belirlediğiniz şifre"}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-primary" onClick={() => setShowInfoModal(null)}>Tamam</button>
|
||||
<button type="button" className="btn btn-primary" onClick={() => setShowInfoModal(null)}>{dict.mailboxes.ok || "Tamam"}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user