feat: complete i18n support, telegram webhook, and security improvements

This commit is contained in:
AyrisAI
2026-05-14 13:46:17 +03:00
parent 4c9a07e3ef
commit cc65a2bd72
23 changed files with 798 additions and 205 deletions

View File

@@ -2,6 +2,7 @@
import { useState, useEffect, useTransition } from "react";
import { formatBytes } from "@/lib/format";
import { useDictionary } from "@/components/DictionaryContext";
interface Domain {
domain_name: string;
@@ -23,6 +24,7 @@ export default function DomainsPage() {
const [search, setSearch] = useState("");
const [form, setForm] = useState({ domain: "", description: "", mailboxes: "10", quota: "10240", maxquota: "10240" });
const [formError, setFormError] = useState("");
const dict = useDictionary();
const fetchDomains = async () => {
setLoading(true);
@@ -54,14 +56,23 @@ export default function DomainsPage() {
fetchDomains();
} else {
const data = await res.json();
const msg = Array.isArray(data) ? data.map((d: { msg?: string }) => d.msg).join(", ") : (data?.error ?? "Bir hata oluştu");
setFormError(String(msg));
let msg = "Bir hata oluştu";
if (Array.isArray(data)) {
msg = data.map((d: any) => {
if (typeof d.msg === "string") return d.msg;
if (Array.isArray(d.msg)) return d.msg.join(", ");
return JSON.stringify(d.msg || d);
}).join(" | ");
} else if (data?.error) {
msg = data.error;
}
setFormError(msg);
}
});
};
const handleDelete = (domain: string) => {
if (!confirm(`"${domain}" domainini Mailcow'dan silmek istediğinizden emin misiniz?\n\nBu işlem geri alınamaz!`)) return;
if (!confirm(`"${domain}" domainini silmek istediğinizden emin misiniz?\n\nBu işlem geri alınamaz!`)) return;
startTransition(async () => {
await fetch(`/api/domains/${encodeURIComponent(domain)}`, { method: "DELETE" });
fetchDomains();
@@ -78,11 +89,11 @@ export default function DomainsPage() {
<>
<div className="page-header">
<div>
<h1 className="page-title">Domainler</h1>
<p className="page-subtitle">Mailcow üzerindeki tüm domainleri yönetin</p>
<h1 className="page-title">{dict.domains.title || "Domainler"}</h1>
<p className="page-subtitle">{domains.length} {dict.domains.subtitle || "domain listeleniyor"}</p>
</div>
<button className="btn btn-primary" onClick={() => setShowModal(true)}>
<PlusIcon /> Domain Ekle
<PlusIcon /> {dict.domains.addDomain || "Domain Ekle"}
</button>
</div>
@@ -93,13 +104,13 @@ export default function DomainsPage() {
<input
type="text"
className="input search-input"
placeholder="Domain veya açıklama ara..."
placeholder={dict.domains.searchPlaceholder || "Domain ara..."}
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
<button className="btn btn-ghost" onClick={fetchDomains}>
<RefreshIcon /> Yenile
<RefreshIcon /> {dict.domains.refresh || "Yenile"}
</button>
</div>
@@ -109,19 +120,19 @@ export default function DomainsPage() {
) : filtered.length === 0 ? (
<div className="empty-state">
<div className="empty-icon"><GlobeIcon size={24} /></div>
<div style={{ fontWeight: 600 }}>Domain bulunamadı</div>
<div style={{ fontSize: 12 }}>Mailcow&apos;a domain eklemek için &quot;Domain Ekle&quot; butonuna tıklayın.</div>
<div style={{ fontWeight: 600 }}>{dict.domains.noDomains || "Domain bulunamadı"}</div>
<div style={{ fontSize: 12 }}>{dict.domains.tryDiffSearch || "Farklı bir arama yapın."}</div>
</div>
) : (
<table>
<thead>
<tr>
<th>Domain</th>
<th>Mail Kutuları</th>
<th>Alias</th>
<th>Kota</th>
<th>Durum</th>
<th>İşlemler</th>
<th>{dict.domains.domain || "Domain"}</th>
<th>{dict.domains.mailboxes || "Mail Kutuları"}</th>
<th>{dict.domains.aliases || "Alias"}</th>
<th>{dict.domains.quota || "Kota"}</th>
<th>{dict.domains.status || "Durum"}</th>
<th>{dict.domains.actions || "İşlemler"}</th>
</tr>
</thead>
<tbody>
@@ -160,7 +171,7 @@ export default function DomainsPage() {
</td>
<td>
<span className={`badge ${String(d.active) === "1" ? "badge-green" : "badge-red"}`}>
{String(d.active) === "1" ? "● Aktif" : "● Pasif"}
{String(d.active) === "1" ? `${dict.domains.active || "Aktif"}` : `${dict.domains.inactive || "Pasif"}`}
</span>
</td>
<td>
@@ -181,20 +192,20 @@ export default function DomainsPage() {
<div className="modal-overlay" onClick={(e) => e.target === e.currentTarget && setShowModal(false)}>
<div className="modal">
<div className="modal-header">
<h2 className="modal-title">Mailcow&apos;a Domain Ekle</h2>
<h2 className="modal-title">{dict.domains.addDomain || "Domain Ekle"}</h2>
<button className="modal-close" onClick={() => setShowModal(false)}><XIcon /></button>
</div>
<form onSubmit={handleCreate}>
<div className="modal-body form-group">
{formError && <div className="error-msg">{formError}</div>}
<div>
<label className="label">Domain Adı</label>
<label className="label">{dict.domains.domain || "Domain Adı"}</label>
<input type="text" className="input" placeholder="ornek.com" value={form.domain}
onChange={(e) => setForm({ ...form, domain: e.target.value })} required />
</div>
<div>
<label className="label">ıklama (isteğe bağlı)</label>
<input type="text" className="input" placeholder="Bu domain hakkında..." value={form.description}
<label className="label">{dict.domains.description || "Açıklama"}</label>
<input type="text" className="input" placeholder="" value={form.description}
onChange={(e) => setForm({ ...form, description: e.target.value })} />
</div>
<div className="form-row">