fix: persist notification preferences and clarify delivery controls
This commit is contained in:
@@ -2,12 +2,11 @@ import {DeliveryQueue} from './DeliveryQueue';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useApp } from '../../context/AppContext';
|
||||
import { Smartphone, Wifi, WifiOff, Download, Bell, CheckCircle2, Moon, Mail, Send } from 'lucide-react';
|
||||
import confetti from 'canvas-confetti';
|
||||
import { getToken } from '../../services/auth';
|
||||
import { NotifySubscriptions } from './NotifySubscriptions';
|
||||
|
||||
export const PwaSettings = () => {
|
||||
const { userProfile, authUser, tenders, reminderSettings, setReminderSettings, requestPushPermission, notify, isQuietNow } = useApp();
|
||||
const { userProfile, authUser, tenders, reminderSettings, saveReminderSettings, reminderSaving, reminderError, requestPushPermission, notify, isQuietNow } = useApp();
|
||||
// Gerçek Cache API sayımı — sahte "102 ihale önbellekte" yerine
|
||||
const [onbellekSayisi, setOnbellekSayisi] = useState(null);
|
||||
const [onbellekAdi, setOnbellekAdi] = useState('');
|
||||
@@ -73,12 +72,10 @@ export const PwaSettings = () => {
|
||||
const p = await requestPushPermission();
|
||||
setPermission(p);
|
||||
if (p === 'granted') {
|
||||
setReminderSettings((s) => ({ ...s, pushEnabled: true }));
|
||||
notify('Bildirimler açıldı', 'Yeni ihale ve son tarih uyarıları buradan gelecek.');
|
||||
confetti({ particleCount: 50 });
|
||||
await saveReminderSettings((s) => ({ ...s, pushEnabled: true }));
|
||||
}
|
||||
};
|
||||
const setQuiet = (patch) => setReminderSettings((s) => ({ ...s, ...patch }));
|
||||
const setQuiet = (patch) => saveReminderSettings((s) => ({ ...s, ...patch }));
|
||||
|
||||
// Gerçek PWA kurulumu: tarayıcı beforeinstallprompt verirse yerel diyalog;
|
||||
// zaten standalone açıldıysa "yüklü"; iOS Safari'de olay yok → yol tarifi.
|
||||
@@ -173,36 +170,42 @@ export const PwaSettings = () => {
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 leading-relaxed">
|
||||
Tarayıcı bildirimlerine izin verin; yeni ihale ve son tarih uyarıları gerçek bildirim olarak gelir.
|
||||
Bu uygulama açıkken alınan uyarıları tarayıcı bildirimi olarak gösterebilirsiniz. Kapalı uygulamaya arka plan push teslimi sağlanmaz.
|
||||
</p>
|
||||
|
||||
{permission !== 'granted' ? (
|
||||
<button onClick={handleEnablePush} disabled={permission === 'unsupported'}
|
||||
<button onClick={handleEnablePush} disabled={reminderSaving || permission === 'unsupported'}
|
||||
className="w-full bg-amber-500 hover:bg-amber-400 disabled:opacity-40 text-white font-bold text-xs py-2.5 rounded-xl">
|
||||
Bildirimlere İzin Ver
|
||||
</button>
|
||||
) : (
|
||||
<button onClick={() => notify('Test bildirimi', isQuietNow() ? 'Sessiz saatte — sadece listeye eklendi.' : 'Bildirimler çalışıyor ✓')}
|
||||
<button onClick={() => notify('Test bildirimi', isQuietNow() || !reminderSettings.pushEnabled ? 'Tarayıcı bildirimi kapalı veya sessiz saatte — listeye eklendi.' : 'Tarayıcı bildirimi gösterim denemesi.')}
|
||||
className="w-full bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs py-2.5 rounded-xl">
|
||||
Test Bildirimi Gönder
|
||||
</button>
|
||||
)}
|
||||
|
||||
{reminderError&&<p role="alert" className="text-xs text-red-700">{reminderError}</p>}
|
||||
{reminderSaving&&<p role="status" className="text-xs">Bildirim ayarları kaydediliyor…</p>}
|
||||
<label className="flex justify-between text-xs font-bold">Tarayıcı bildirimlerini göster
|
||||
<input type="checkbox" checked={reminderSettings.pushEnabled} disabled={reminderSaving} onChange={e=>setQuiet({pushEnabled:e.target.checked})}/>
|
||||
</label>
|
||||
{/* Sessiz saatler — gerçek */}
|
||||
<div className="pt-2 border-t border-slate-100 space-y-2">
|
||||
<p className="text-xs text-slate-500">Saatler bu cihazın yerel saatine göredir. Başlangıç ve bitiş aynıysa sessiz süre uygulanmaz.</p>
|
||||
<label className="flex items-center justify-between text-xs font-bold text-slate-800 cursor-pointer">
|
||||
<span className="flex items-center gap-1.5"><Moon className="w-3.5 h-3.5 text-indigo-500" /> Sessiz Saatler</span>
|
||||
<input type="checkbox" checked={reminderSettings.quietEnabled}
|
||||
<input type="checkbox" disabled={reminderSaving} checked={reminderSettings.quietEnabled}
|
||||
onChange={(e) => setQuiet({ quietEnabled: e.target.checked })}
|
||||
className="w-4 h-4 accent-indigo-600 rounded" />
|
||||
</label>
|
||||
{reminderSettings.quietEnabled && (
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<input type="time" value={reminderSettings.quietStart}
|
||||
<input type="time" aria-label="Sessiz saat başlangıcı" disabled={reminderSaving} value={reminderSettings.quietStart}
|
||||
onChange={(e) => setQuiet({ quietStart: e.target.value })}
|
||||
className="bg-slate-50 border border-slate-300 rounded-lg px-2 py-1.5 flex-1" />
|
||||
<span className="text-slate-400">→</span>
|
||||
<input type="time" value={reminderSettings.quietEnd}
|
||||
<input type="time" aria-label="Sessiz saat bitişi" disabled={reminderSaving} value={reminderSettings.quietEnd}
|
||||
onChange={(e) => setQuiet({ quietEnd: e.target.value })}
|
||||
className="bg-slate-50 border border-slate-300 rounded-lg px-2 py-1.5 flex-1" />
|
||||
</div>
|
||||
@@ -234,12 +237,7 @@ export const PwaSettings = () => {
|
||||
: '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>
|
||||
<p className="text-xs text-slate-600">E-posta gönderimlerini aşağıdaki bildirim aboneliklerinden yönetin. Tarayıcı bildirim seçimi ve sessiz saatler e-posta aboneliklerini kapatmaz.</p>
|
||||
|
||||
<button
|
||||
onClick={sendTestEmail}
|
||||
|
||||
+19
-16
@@ -1,3 +1,4 @@
|
||||
import {validateReminderPreferences,quietNow} from '../services/reminderPreferences';
|
||||
import {validateTeam} from '../services/teamData';
|
||||
import {savedFilterCriteria} from '../services/savedFilterCriteria';
|
||||
import {mergeServerNotifications,acknowledgeNotifications} from '../services/notifications';
|
||||
@@ -166,7 +167,7 @@ export const AppProvider = ({ children }) => {
|
||||
useEffect(() => {
|
||||
notificationVersion.current++;notificationMutation.current=null;seenServerNotifications.current.clear();setNotificationError(null);setNotificationBusy(false);
|
||||
aiRunRef.current=null;setAiAnalyzing(false);setAiJobStatus('');
|
||||
dataRevisions.current={};setTeamError(null);setCompetitorError(null);
|
||||
dataRevisions.current={};setTeamError(null);setCompetitorError(null);setReminderError(null);
|
||||
setVeriSenkron(false); setPlan(null); setSaveError(null);
|
||||
setFavoriteKurumlar([]); setTeamMembers([]); setSavedFilters([]); setCompetitorWatchlist([]);
|
||||
setReminderSettings(defaultReminder()); setUserProfile(defaultProfile(authUser));
|
||||
@@ -409,20 +410,22 @@ export const AppProvider = ({ children }) => {
|
||||
const [vaultDocuments, setVaultDocuments] = useState([]);
|
||||
|
||||
const [reminderSettings, setReminderSettings] = useState(defaultReminder);
|
||||
useEffect(() => {
|
||||
if (veriSenkron) ilkYazmaDegil('reminder', reminderSettings);
|
||||
}, [reminderSettings, veriSenkron]);
|
||||
|
||||
// Şu an sessiz saat aralığında mıyız (gece yarısını geçen aralıkları da destekler)
|
||||
const isQuietNow = () => {
|
||||
if (!reminderSettings.quietEnabled) return false;
|
||||
const now = new Date();
|
||||
const cur = now.getHours() * 60 + now.getMinutes();
|
||||
const [sh, sm] = reminderSettings.quietStart.split(':').map(Number);
|
||||
const [eh, em] = reminderSettings.quietEnd.split(':').map(Number);
|
||||
const start = sh * 60 + sm, end = eh * 60 + em;
|
||||
return start <= end ? cur >= start && cur < end : cur >= start || cur < end;
|
||||
const reminderSaveLock=useRef(false),reminderCurrent=useRef(reminderSettings);
|
||||
reminderCurrent.current=reminderSettings;
|
||||
const [reminderSaving,setReminderSaving]=useState(false),[reminderError,setReminderError]=useState(null);
|
||||
const saveReminderSettings=async change=>{
|
||||
if(!authUser||!veriSenkron){setReminderError('Hesap verileri yüklenmeden bildirim ayarları kaydedilemez.');return false;}
|
||||
if(reminderSaveLock.current)return false;
|
||||
const session=getToken();reminderSaveLock.current=true;setReminderSaving(true);setReminderError(null);
|
||||
try {
|
||||
const next=validateReminderPreferences(typeof change==='function'?change(reminderCurrent.current):change);
|
||||
const saved=await setUserData('reminder',next,dataRevisions.current.reminder ?? null);
|
||||
if(getToken()!==session)return false;
|
||||
dataRevisions.current.reminder=saved.revision;reminderCurrent.current=next;setReminderSettings(next);return true;
|
||||
}catch(error){if(getToken()===session)setReminderError(error.message);return false;}
|
||||
finally{reminderSaveLock.current=false;setReminderSaving(false);}
|
||||
};
|
||||
const isQuietNow=()=>quietNow(reminderCurrent.current);
|
||||
|
||||
const [webhooksConfig, setWebhooksConfig] = useState({
|
||||
slackWebhookUrl: '',
|
||||
@@ -580,7 +583,7 @@ export const AppProvider = ({ children }) => {
|
||||
pushNotification(title,text);
|
||||
};
|
||||
const pushNotification=(title,text)=>{
|
||||
if (isQuietNow() || !reminderSettings.pushEnabled) return;
|
||||
if (isQuietNow() || !reminderCurrent.current.pushEnabled) return;
|
||||
try {
|
||||
if ('Notification' in window && Notification.permission === 'granted') {
|
||||
new Notification(title, { body: text, icon: '/favicon.ico' });
|
||||
@@ -826,7 +829,7 @@ export const AppProvider = ({ children }) => {
|
||||
vaultDocuments,
|
||||
setVaultDocuments,
|
||||
reminderSettings,
|
||||
setReminderSettings,
|
||||
saveReminderSettings,reminderSaving,reminderError,
|
||||
webhooksConfig,
|
||||
setWebhooksConfig,
|
||||
apiKeys,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
export function validateReminderPreferences(value) {
|
||||
if(!value || typeof value!=='object' || Array.isArray(value))throw new Error('Bildirim ayarları geçersiz.');
|
||||
for(const key of ['pushEnabled','quietEnabled'])if(typeof value[key]!=='boolean')throw new Error('Bildirim seçimini kontrol edin.');
|
||||
for(const key of ['quietStart','quietEnd'])if(typeof value[key]!=='string'||!/^([01]\d|2[0-3]):[0-5]\d$/.test(value[key]))throw new Error('Sessiz saatleri saat:dakika biçiminde girin.');
|
||||
return value;
|
||||
}
|
||||
export function quietNow(preferences,now=new Date()) {
|
||||
if(!preferences?.quietEnabled)return false;
|
||||
try {validateReminderPreferences(preferences);}catch{return true;}
|
||||
const minute=text=>Number(text.slice(0,2))*60+Number(text.slice(3));
|
||||
const start=minute(preferences.quietStart),end=minute(preferences.quietEnd),current=now.getHours()*60+now.getMinutes();
|
||||
return start<=end ? current>=start&¤t<end : current>=start||current<end;
|
||||
}
|
||||
Reference in New Issue
Block a user