feat: tüm mock veriyi kaldır + Resend e-posta entegrasyonu

- mockTenders.js silindi; referans listeleri (sektör/il/kurum) constants.js'e taşındı
- AppContext: tenders/liveStats/competitorWatchlist/vaultDocuments/notifications/
  savedFilters/apiKeys/scrapedCapabilities → gerçek/boş; sahte Math.random online
  sayacı kaldırıldı; competitor & belge kasası artık localStorage
- LiveStatsBar: gerçek alanlar (canlı havuz/yüklü/bugün/kalem/takibimdeki)
- AnalyticsDashboard: KPI'lar (katılım/kazanma oranı/kazanılan bedel) + aylık hacim
  + sektörel dağılım gerçek companyStats/monthlyStats/sectorStats'tan; ciro hedefi
  kullanıcı tarafından belirlenir; veri yoksa dürüst boş durum
- ContractSearch & Rakip Radar: gerçek firmStats
- analytics.js: companyStats/monthlyStats/parseDateTR eklendi
- Resend: server/email.mjs + /api/notify/email/config + /api/notify/email/test
  (auth'lu, kullanıcının kendi adresine); PwaSettings gerçek e-posta test kartı
This commit is contained in:
2026-08-28 08:49:10 +03:00
parent 7521f798f6
commit 3e129c2988
14 changed files with 497 additions and 555 deletions
+82 -3
View File
@@ -1,14 +1,50 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { useApp } from '../../context/AppContext';
import { Smartphone, Wifi, WifiOff, Download, Bell, CheckCircle2, ShieldCheck, Zap, Sparkles, Moon } from 'lucide-react';
import { Smartphone, Wifi, WifiOff, Download, Bell, CheckCircle2, Moon, Mail, Send } from 'lucide-react';
import confetti from 'canvas-confetti';
import { getToken } from '../../services/auth';
export const PwaSettings = () => {
const { userProfile, reminderSettings, setReminderSettings, requestPushPermission, notify, isQuietNow } = useApp();
const { userProfile, authUser, tenders, reminderSettings, setReminderSettings, requestPushPermission, notify, isQuietNow } = useApp();
const [offlineMode, setOfflineMode] = useState(true);
const [installed, setInstalled] = useState(false);
const [permission, setPermission] = useState(typeof Notification !== 'undefined' ? Notification.permission : 'unsupported');
// E-posta (Resend) durumu + test gönderimi
const [emailCfg, setEmailCfg] = useState(null); // null=bilinmiyor, true/false
const [emailSending, setEmailSending] = useState(false);
const [emailResult, setEmailResult] = useState(null);
useEffect(() => {
fetch('/api/notify/email/config')
.then((r) => r.json())
.then((d) => setEmailCfg(Boolean(d.configured)))
.catch(() => setEmailCfg(false));
}, []);
const sendTestEmail = async () => {
setEmailSending(true);
setEmailResult(null);
try {
const yeniler = tenders.filter((t) => t.isYeni).slice(0, 15);
const res = await fetch('/api/notify/email/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getToken()}` },
body: JSON.stringify({ subject: 'İhaleist — bildirim testi', tenders: yeniler }),
});
const data = await res.json();
if (data.success) {
setEmailResult({ ok: true, msg: `E-posta gönderildi → ${data.to}` });
notify('E-posta gönderildi', `Test e-postası ${data.to} adresine iletildi.`);
} else {
setEmailResult({ ok: false, msg: data.error || 'Gönderilemedi' });
}
} catch (e) {
setEmailResult({ ok: false, msg: e.message });
} finally {
setEmailSending(false);
}
};
const handleEnablePush = async () => {
const p = await requestPushPermission();
setPermission(p);
@@ -134,6 +170,49 @@ export const PwaSettings = () => {
</div>
</div>
{/* E-posta Bildirimi (Resend) — gerçek gönderim */}
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-xs space-y-4">
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
<h3 className="font-extrabold text-sm text-slate-900 flex items-center gap-2">
<Mail className="w-4 h-4 text-blue-500" /> E-posta Bildirimi
</h3>
<span className={`font-extrabold text-[10px] px-2 py-0.5 rounded-full ${
emailCfg === true ? 'bg-emerald-100 text-emerald-800' : emailCfg === false ? 'bg-amber-100 text-amber-800' : 'bg-slate-100 text-slate-600'
}`}>
{emailCfg === true ? 'Aktif (Resend)' : emailCfg === false ? 'Yapılandırılmadı' : 'Kontrol ediliyor…'}
</span>
</div>
<p className="text-xs text-slate-600 leading-relaxed">
{emailCfg === false
? 'E-posta göndermek için sunucuya RESEND_API_KEY ekleyin (Coolify → Environment). Ayrıca gönderen domainini Resend’de DNS ile doğrulayın.'
: 'Takip kriterlerinize uyan ihaleler, hesabınızın e-posta adresine özet olarak gönderilir. Aşağıdan kendinize test gönderebilirsiniz.'}
</p>
<label className="flex items-center justify-between text-xs font-bold text-slate-800 cursor-pointer">
<span>E-posta bildirimleri açık</span>
<input type="checkbox" checked={reminderSettings.emailEnabled}
onChange={(e) => setReminderSettings((s) => ({ ...s, emailEnabled: e.target.checked }))}
className="w-4 h-4 accent-blue-600 rounded" />
</label>
<button
onClick={sendTestEmail}
disabled={emailSending || emailCfg !== true || !authUser}
className="w-full bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white font-bold text-xs py-2.5 rounded-xl flex items-center justify-center gap-2"
title={!authUser ? 'Giriş yapın' : emailCfg !== true ? 'RESEND_API_KEY gerekli' : 'Kendinize test e-postası gönder'}
>
<Send className="w-3.5 h-3.5" />
{emailSending ? 'Gönderiliyor…' : 'Kendime Test E-postası Gönder'}
</button>
{emailResult && (
<p className={`text-[11px] ${emailResult.ok ? 'text-emerald-600' : 'text-red-600'}`}>
{emailResult.ok ? '✓ ' : '⚠ '}{emailResult.msg}
</p>
)}
</div>
{/* Install PWA Application */}
<div className="bg-linear-to-br from-slate-900 to-cyan-950 text-white p-6 rounded-2xl shadow-xs space-y-4 border border-cyan-800/40">
<div className="flex items-center justify-between">