This commit is contained in:
everythingonblack
2025-06-18 01:10:51 +07:00
parent 64f5609d2c
commit 518bf076a3
3 changed files with 136 additions and 9 deletions

View File

@@ -7,6 +7,7 @@ import Conversations from './Conversations';
import DiscussedTopics from './DiscussedTopics';
import Chart from 'chart.js/auto';
import NotificationPrompt from './NotificationPrompt';
const Dashboard = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
@@ -46,6 +47,25 @@ const Dashboard = () => {
const handleLogout = () => {
localStorage.removeItem('token');
localStorage.removeItem('user');
navigator.serviceWorker.ready.then(function (registration) {
registration.pushManager.getSubscription().then(function (subscription) {
if (subscription) {
subscription.unsubscribe().then(function (successful) {
console.log('Push subscription unsubscribed on logout:', successful);
// Optional: also notify backend to clear the token
fetch('/api/clear-subscription', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ endpoint: subscription.endpoint }),
});
});
}
});
});
window.location.reload();
};
@@ -79,8 +99,7 @@ const Dashboard = () => {
});
if (response.status === 401 || response.status === 403) {
localStorage.removeItem('token');
navigate('/login');
handleLogout();
return;
}
@@ -126,8 +145,19 @@ const Dashboard = () => {
};
if (!checkOnce && 'serviceWorker' in navigator) {
subscribeUser();
setCheckOnce(false);
navigator.serviceWorker.ready.then(function (registration) {
registration.pushManager.getSubscription().then(function (subscription) {
setCheckOnce(false);
if (subscription === null) {
// Not subscribed yet — show modal asking user to subscribe
setModalContent(<NotificationPrompt onAllow={subscribeUser} onDismiss={null} />);
} else {
// Already subscribed
setModalContent('')
console.log('User is already subscribed.');
}
});
});
}
fetchData(); // Jalankan langsung saat komponen di-mount
@@ -147,7 +177,7 @@ const Dashboard = () => {
});
const token = localStorage.getItem('token');
await fetch('https://bot.kediritechnopark.com/webhook/subscribe', {
method: 'POST',
body: JSON.stringify({
@@ -159,6 +189,7 @@ const Dashboard = () => {
},
});
setModalContent('')
};
function urlBase64ToUint8Array(base64String) {
@@ -189,17 +220,22 @@ const Dashboard = () => {
const prefixLabelMap = {
WEB: 'Web App',
TGG: 'Telegram',
DME: 'Instagram',
WGG: 'Whatsapp',
};
const prefixColors = {
WEB: { border: '#4285F4', background: 'rgba(66, 133, 244, 0.2)' },
TGG: { border: '#25D366', background: 'rgba(37, 211, 102, 0.2)' },
DME: { border: '#AA00FF', background: 'rgba(170, 0, 255, 0.2)' },
WGG: { border: '#AA00FF', background: 'rgba(170, 0, 255, 0.2)' },
};
const prefixes = Object.keys(prefixLabelMap);
const hours = rawData.map(d => d.hour).sort((a, b) => parseFloat(a) - parseFloat(b));
const hours = rawData.map(d => d.hour.split(' ')[1]).sort((a, b) => {
// Sort berdasarkan jam dan menit
const [h1, m1] = a.split(':').map(Number);
const [h2, m2] = b.split(':').map(Number);
return h1 !== h2 ? h1 - h2 : m1 - m2;
});
const counts = {};
prefixes.forEach(prefix => {
@@ -298,7 +334,7 @@ const Dashboard = () => {
<div className={styles.statsGrid}>
<div className={styles.statCard} onClick={openConversationsModal}>
<h2>{stats.totalChats}</h2>
<p>Total Percakapan Hari Ini</p>
<p>Total Percakapan selama 24 jam</p>
</div>
<div className={styles.statCard}>
<h2>{stats.userMessages}</h2>

23
src/NotificationPrompt.js Normal file
View File

@@ -0,0 +1,23 @@
import React from 'react';
import styles from './NotificationPrompt.module.css';
export default function NotificationPrompt({ onAllow, onDismiss }) {
return (
<div className={styles.backdrop}>
<div className={styles.modal}>
<h2 className={styles.title}>Enable Notifications</h2>
<p className={styles.description}>
Stay up to date with important updates and alerts. Enable push notifications to never miss a thing.
</p>
<div className={styles.actions}>
<button onClick={onAllow} className={styles.primaryButton}>
Enable Notifications
</button>
<button onClick={onDismiss} className={styles.secondaryButton}>
Maybe Later
</button>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,68 @@
.backdrop {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.45);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.modal {
background-color: #fff;
border-radius: 8px;
padding: 32px;
max-width: 420px;
width: 90%;
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
text-align: center;
}
.title {
margin-bottom: 16px;
font-size: 22px;
font-weight: 600;
color: #1f2937;
}
.description {
margin-bottom: 24px;
font-size: 16px;
color: #4b5563;
line-height: 1.5;
}
.actions {
display: flex;
flex-direction: column;
gap: 12px;
}
.primaryButton {
background-color: #0073bb;
color: white;
font-weight: 500;
padding: 12px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s;
}
.primaryButton:hover {
background-color: #005a99;
}
.secondaryButton {
background-color: transparent;
color: #374151;
border: 1px solid #d1d5db;
padding: 12px 20px;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s;
}
.secondaryButton:hover {
background-color: #f3f4f6;
}