Compare commits
6 Commits
b0139b6cab
...
47dced6f89
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47dced6f89 | ||
|
|
25cc2227c5 | ||
|
|
effd88adfe | ||
|
|
8aa8410d48 | ||
|
|
7f1a81977f | ||
|
|
1098668dc4 |
196
app/[lang]/dashboard/settings/page.tsx
Normal file
196
app/[lang]/dashboard/settings/page.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useDictionary } from "@/components/DictionaryContext";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
export default function SettingsPage() {
|
||||
const { data: session } = useSession();
|
||||
const [profile, setProfile] = useState<any>(null);
|
||||
const [waStatus, setWaStatus] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [fetchingQr, setFetchingQr] = useState(false);
|
||||
const dict = useDictionary();
|
||||
|
||||
useEffect(() => {
|
||||
fetchProfile();
|
||||
fetchWaStatus();
|
||||
|
||||
const interval = setInterval(() => {
|
||||
fetchWaStatus();
|
||||
}, 5000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const fetchProfile = async () => {
|
||||
try {
|
||||
const res = await fetch("/api/users/profile");
|
||||
const data = await res.json();
|
||||
if (data && data.error) setError(data.error);
|
||||
else if (!data) setError("Kullanıcı profili bulunamadı.");
|
||||
else setProfile(data);
|
||||
} catch (e: any) {
|
||||
setError(e.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchWaStatus = async () => {
|
||||
try {
|
||||
const res = await fetch("/api/whatsapp/status");
|
||||
const data = await res.json();
|
||||
setWaStatus(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConnectWa = async () => {
|
||||
setFetchingQr(true);
|
||||
try {
|
||||
const res = await fetch("/api/whatsapp/qr");
|
||||
const data = await res.json();
|
||||
setWaStatus(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
setFetchingQr(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await fetch("/api/users/profile", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(profile)
|
||||
});
|
||||
if (res.ok) alert("Ayarlar kaydedildi!");
|
||||
else {
|
||||
const data = await res.json();
|
||||
alert("Hata: " + (data?.error || "Bilinmeyen bir hata oluştu."));
|
||||
}
|
||||
} catch (e: any) {
|
||||
alert("Hata: " + e.message);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return <div className="page-body"><span className="spinner" /></div>;
|
||||
if (error) return <div className="page-body"><div className="card" style={{ color: "var(--error)" }}>Hata: {error}</div></div>;
|
||||
if (!profile) return <div className="page-body">Profil yüklenemedi.</div>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<h1 className="page-title">Bildirim Ayarları</h1>
|
||||
<p className="page-subtitle">Telegram ve WhatsApp bildirimlerinizi buradan yönetin.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="page-body" style={{ maxWidth: 800 }}>
|
||||
<form onSubmit={handleSave} className="form-group">
|
||||
<div className="card">
|
||||
<h3 style={{ marginBottom: 20, display: "flex", alignItems: "center", gap: 10 }}>
|
||||
<TelegramIcon /> Telegram Bildirimleri
|
||||
</h3>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 20 }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="tg-enabled"
|
||||
checked={profile.telegramEnabled}
|
||||
onChange={e => setProfile({...profile, telegramEnabled: e.target.checked})}
|
||||
/>
|
||||
<label htmlFor="tg-enabled">Telegram bildirimlerini aktif et</label>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Telegram ID</label>
|
||||
<input
|
||||
className="input"
|
||||
value={profile.telegramId || ""}
|
||||
onChange={e => setProfile({...profile, telegramId: e.target.value})}
|
||||
placeholder="Örn: 5009005027"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card" style={{ marginTop: 24 }}>
|
||||
<h3 style={{ marginBottom: 20, display: "flex", alignItems: "center", gap: 10 }}>
|
||||
<WhatsAppIcon /> WhatsApp Bildirimleri
|
||||
</h3>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 20 }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="wa-enabled"
|
||||
checked={profile.whatsappEnabled}
|
||||
onChange={e => setProfile({...profile, whatsappEnabled: e.target.checked})}
|
||||
/>
|
||||
<label htmlFor="wa-enabled">WhatsApp bildirimlerini aktif et</label>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24 }}>
|
||||
<div>
|
||||
<label className="label">Telefon Numarası</label>
|
||||
<input
|
||||
className="input"
|
||||
value={profile.whatsappNumber || ""}
|
||||
onChange={e => setProfile({...profile, whatsappNumber: e.target.value})}
|
||||
placeholder="90554XXXXXXX"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ borderLeft: "1px solid var(--border)", paddingLeft: 24 }}>
|
||||
<label className="label">Bağlantı Durumu</label>
|
||||
{waStatus?.status === 'connected' ? (
|
||||
<div style={{ color: "#10b981", fontWeight: 600, display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<div style={{ width: 8, height: 8, borderRadius: "50%", background: "#10b981" }} />
|
||||
Bağlı ✅
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div style={{ color: "#ef4444", fontWeight: 600, display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
|
||||
<div style={{ width: 8, height: 8, borderRadius: "50%", background: "#ef4444" }} />
|
||||
Bağlı Değil
|
||||
</div>
|
||||
{waStatus?.qr ? (
|
||||
<div style={{ background: "#fff", padding: 10, borderRadius: 8, width: "fit-content" }}>
|
||||
<img src={waStatus.qr} alt="QR Code" style={{ width: 150, height: 150 }} />
|
||||
<p style={{ fontSize: 11, color: "#000", textAlign: "center", marginTop: 5 }}>WhatsApp'tan okutun</p>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-sm"
|
||||
onClick={handleConnectWa}
|
||||
disabled={fetchingQr}
|
||||
>
|
||||
{fetchingQr ? "QR Oluşturuluyor..." : "Bağlantı Kur (QR)"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 24, display: "flex", justifyContent: "flex-end" }}>
|
||||
<button type="submit" className="btn btn-primary" disabled={saving}>
|
||||
{saving ? <span className="spinner" /> : "Değişiklikleri Kaydet"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TelegramIcon() { return <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/></svg>; }
|
||||
function WhatsAppIcon() { return <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5Z"/></svg>; }
|
||||
38
app/api/users/profile/route.ts
Normal file
38
app/api/users/profile/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export async function GET() {
|
||||
const session = await auth();
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: session.user.id }
|
||||
});
|
||||
|
||||
return NextResponse.json(user);
|
||||
}
|
||||
|
||||
export async function PATCH(req: Request) {
|
||||
const session = await auth();
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { telegramId, telegramEnabled, whatsappNumber, whatsappEnabled } = body;
|
||||
|
||||
const user = await prisma.user.update({
|
||||
where: { id: session.user.id },
|
||||
data: {
|
||||
telegramId,
|
||||
telegramEnabled,
|
||||
whatsappNumber,
|
||||
whatsappEnabled
|
||||
} as any
|
||||
});
|
||||
|
||||
return NextResponse.json(user);
|
||||
} catch (error: any) {
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -67,36 +67,45 @@ export async function POST(request: Request) {
|
||||
};
|
||||
|
||||
// 3. Bildirim Gönder (Telegram)
|
||||
const notificationResult = await sendTelegramNotification(
|
||||
mapping.userId,
|
||||
to,
|
||||
mailData.from,
|
||||
mailData.subject,
|
||||
"" // Analiz bilgisini kaldırdık
|
||||
);
|
||||
let tgStatus = 'SKIPPED';
|
||||
const user = mapping.user as any;
|
||||
if (user.telegramEnabled && user.telegramId) {
|
||||
const tgResult = await sendTelegramNotification(
|
||||
mapping.userId,
|
||||
to,
|
||||
mailData.from,
|
||||
mailData.subject,
|
||||
""
|
||||
);
|
||||
tgStatus = tgResult.status;
|
||||
}
|
||||
|
||||
// 4. Bildirim Gönder (WhatsApp)
|
||||
// Şu an için varsayılan numaraya gönderiyoruz, ilerde User modeline alan eklenebilir.
|
||||
const waNumber = process.env.DEFAULT_WHATSAPP_NUMBER || '905543765103';
|
||||
const waMessage = `📩 *Yeni E-posta*\n\n*Gönderen:* ${mailData.from}\n*Konu:* ${mailData.subject}\n*Alıcı:* ${to}\n\n_AyrisMail Central_`;
|
||||
|
||||
await sendWA(waNumber, waMessage);
|
||||
let waStatus = 'SKIPPED';
|
||||
if (user.whatsappEnabled && (user.whatsappNumber || process.env.DEFAULT_WHATSAPP_NUMBER)) {
|
||||
const waNumber = user.whatsappNumber || process.env.DEFAULT_WHATSAPP_NUMBER;
|
||||
const waMessage = `📩 *Yeni E-posta*\n\n*Gönderen:* ${mailData.from}\n*Konu:* ${mailData.subject}\n*Alıcı:* ${to}\n\n_AyrisMail Central_`;
|
||||
|
||||
const waResult = await sendWA(waNumber, waMessage, mapping.userId);
|
||||
waStatus = waResult.success ? 'SENT' : 'FAILED';
|
||||
}
|
||||
|
||||
// 5. Bildirim Logu
|
||||
await prisma.notificationLog.create({
|
||||
await (prisma as any).notificationLog.create({
|
||||
data: {
|
||||
mailbox: to,
|
||||
sender: mailData.from,
|
||||
subject: mailData.subject,
|
||||
status: notificationResult.status,
|
||||
status: tgStatus === 'SENT' || waStatus === 'SENT' ? 'SENT' : 'FAILED',
|
||||
userId: mapping.userId,
|
||||
error: notificationResult.error
|
||||
error: tgStatus === 'FAILED' ? 'TG Failed' : (waStatus === 'FAILED' ? 'WA Failed' : null)
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
notification: notificationResult.status,
|
||||
tgStatus,
|
||||
waStatus,
|
||||
subject: mailData.subject
|
||||
});
|
||||
|
||||
|
||||
19
app/api/whatsapp/qr/route.ts
Normal file
19
app/api/whatsapp/qr/route.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
|
||||
export async function GET() {
|
||||
const session = await auth();
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
const userId = session.user.id;
|
||||
const workerUrl = process.env.WHATSAPP_WORKER_URL;
|
||||
const secret = process.env.WHATSAPP_SECRET;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${workerUrl}/get-qr?userId=${userId}&secret=${secret}`);
|
||||
const data = await res.json();
|
||||
return NextResponse.json(data);
|
||||
} catch (error: any) {
|
||||
return NextResponse.json({ status: 'error', error: error.message });
|
||||
}
|
||||
}
|
||||
19
app/api/whatsapp/status/route.ts
Normal file
19
app/api/whatsapp/status/route.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
|
||||
export async function GET() {
|
||||
const session = await auth();
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
const userId = session.user.id;
|
||||
const workerUrl = process.env.WHATSAPP_WORKER_URL;
|
||||
const secret = process.env.WHATSAPP_SECRET;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${workerUrl}/status?userId=${userId}&secret=${secret}`);
|
||||
const data = await res.json();
|
||||
return NextResponse.json(data);
|
||||
} catch (error: any) {
|
||||
return NextResponse.json({ status: 'error', error: error.message });
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ export default function Sidebar({ dict, lang }: { dict: any; lang: string }) {
|
||||
items: [
|
||||
{ href: `/${lang}/dashboard`, label: dict.dashboard?.title || "Dashboard", icon: HomeIcon, roles: ["SUPER_ADMIN", "DOMAIN_ADMIN"] },
|
||||
{ href: `/${lang}/dashboard/mail`, label: dict.sidebar?.mailClient || "Mail Client", icon: InboxIcon, roles: ["SUPER_ADMIN", "DOMAIN_ADMIN"] },
|
||||
{ href: `/${lang}/dashboard/settings`, label: "Ayarlar", icon: SettingsIcon, roles: ["SUPER_ADMIN", "DOMAIN_ADMIN"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -176,3 +177,12 @@ function ListIcon() {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsIcon() {
|
||||
return (
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ model User {
|
||||
role String @default("DOMAIN_ADMIN") // SUPER_ADMIN or DOMAIN_ADMIN
|
||||
domains String[] @default([]) // ["*"] or list of domains
|
||||
telegramId String?
|
||||
telegramEnabled Boolean @default(true)
|
||||
whatsappNumber String?
|
||||
whatsappEnabled Boolean @default(false)
|
||||
mailboxMappings MailboxMapping[]
|
||||
notificationConfigs NotificationConfig[]
|
||||
notificationLogs NotificationLog[]
|
||||
|
||||
Reference in New Issue
Block a user