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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user