From 64f5609d2c5d6898cffd4c3bdf72d2f89f482967 Mon Sep 17 00:00:00 2001 From: Vassshhh Date: Tue, 17 Jun 2025 21:31:52 +0700 Subject: [PATCH] ok --- public/index.html | 8 +-- public/sw.js | 7 +++ src/App.js | 2 + src/ChatBot.js | 91 ++++++++++++++++++++++++---------- src/ChatBot.module.css | 34 +++++++++++-- src/Dashboard.js | 53 +++++++++++++++++--- src/Dashboard.module.css | 6 +-- src/ResetPassword.js | 104 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 262 insertions(+), 43 deletions(-) create mode 100644 public/sw.js create mode 100644 src/ResetPassword.js diff --git a/public/index.html b/public/index.html index aa069f2..b12b730 100644 --- a/public/index.html +++ b/public/index.html @@ -2,14 +2,14 @@ - + - + - React App + Dermalounge diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..1ac54f0 --- /dev/null +++ b/public/sw.js @@ -0,0 +1,7 @@ +self.addEventListener('push', event => { + const data = event.data.json(); + self.registration.showNotification(data.title, { + body: data.body, + icon: '/dermalounge.jpg', + }); +}); diff --git a/src/App.js b/src/App.js index d1d3d97..faa5b2a 100644 --- a/src/App.js +++ b/src/App.js @@ -3,6 +3,7 @@ import { BrowserRouter, Routes, Route, Navigate, useParams } from 'react-router- import axios from 'axios'; import Dashboard from './Dashboard'; +import ResetPassword from './ResetPassword'; // ⬅️ import komponen reset import TenantDashboard from './TenantDashboard'; import ChatBot from './ChatBot'; import Login from './Login'; @@ -49,6 +50,7 @@ function App() { } /> } /> + } /> {/* ✅ Route /dashboard diproteksi */} { const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); + + + const [isPoppedUp, setIsPoppedUp] = useState(false); + const [name, setName] = useState(''); + const [phoneNumber, setPhoneNumber] = useState(''); useEffect(() => { if (existingConversation && existingConversation.length > 0) { @@ -41,7 +46,7 @@ const ChatBot = ({ existingConversation, readOnly, hh }) => { } }, []); - const sendMessage = async (textOverride = null, tryCount = 0) => { + const sendMessage = async (textOverride = null, name, phoneNumber, tryCount = 0) => { const message = textOverride || input.trim(); if (message === '') return; @@ -60,7 +65,7 @@ const ChatBot = ({ existingConversation, readOnly, hh }) => { const response = await fetch('https://bot.kediritechnopark.com/webhook/master-agent/ask/dev', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ pertanyaan: message, sessionId: JSON.parse(localStorage.getItem('session')).sessionId, lastSeen: new Date().toISOString() }), + body: JSON.stringify({ pertanyaan: message, sessionId: JSON.parse(localStorage.getItem('session')).sessionId, lastSeen: new Date().toISOString(), name: JSON.parse(localStorage.getItem('session')).name, phoneNumber: JSON.parse(localStorage.getItem('session')).phoneNumber }), }); const data = await response.json(); @@ -74,6 +79,12 @@ const ChatBot = ({ existingConversation, readOnly, hh }) => { ...prev, { sender: 'bot', text: botAnswer, time: getTime() }, ]); + + const session = JSON.parse(localStorage.getItem('session')); + + if ((!session || !session.name || !session.phoneNumber) && messages.length > 2) { + setIsPoppedUp(true); // munculkan form input + } setIsLoading(false); } catch (error) { @@ -87,7 +98,7 @@ const ChatBot = ({ existingConversation, readOnly, hh }) => { setIsLoading(false); return; } - setTimeout(() => sendMessage(message, tryCount + 1), 3000); + setTimeout(() => sendMessage(message, name, phoneNumber, tryCount + 1), 3000); console.error('Fetch error:', error); } @@ -157,40 +168,68 @@ const ChatBot = ({ existingConversation, readOnly, hh }) => { onKeyDown={(e) => e.key === 'Enter' && sendMessage()} disabled={isLoading} /> + -
- -
- Untuk bisa membantu Anda lebih jauh, boleh saya tahu nama dan nomor telepon Anda? - Informasi ini juga membantu tim admin kami jika perlu melakukan follow-up nantinya 😊 -
- console.log('Nama focused')} - /> -
- +62 + {isPoppedUp && +
+
+ Untuk bisa membantu Anda lebih jauh, boleh saya tahu nama dan nomor telepon Anda? + Informasi ini juga membantu tim admin kami jika perlu melakukan follow-up nantinya 😊 +
console.log('Telepon focused')} - style={{border: 0, width: '100%'}} + placeholder="Nama Lengkapmu" + onFocus={() => console.log('Nama focused')} + value={name} + onChange={(e) => setName(e.target.value)} + maxLength={40} /> -
+
+ +62 + { + const value = e.target.value; + // Hanya angka, maksimal 11 karakter + if (/^\d{0,11}$/.test(value)) { + setPhoneNumber(value); + } + }} + onFocus={() => console.log('Telepon focused')} + /> -
- Lanjut +
+ +
2 && phoneNumber.length >= 10 ? 'black' : '#ccc' }} + onClick={() => { + if (name.length > 2 && phoneNumber.length >= 10) { + const sessionData = JSON.parse(localStorage.getItem('session')) || {}; + + sessionData.name = name; + sessionData.phoneNumber = phoneNumber; + + localStorage.setItem('session', JSON.stringify(sessionData)); + setIsPoppedUp(false) + } + }} + > + Lanjut +
-
+ }
); }; diff --git a/src/ChatBot.module.css b/src/ChatBot.module.css index eba772f..6d08f27 100644 --- a/src/ChatBot.module.css +++ b/src/ChatBot.module.css @@ -129,18 +129,17 @@ .inputGroup { display: flex; align-items: center; - border: 1px solid #ccc; - border-radius: 20px; - overflow: hidden; width: 100%; } .prefix { background-color: #f0f0f0; padding: 0.5rem 0.75rem; - border-right: 1px solid #ccc; + border-radius: 20px 0px 0px 20px; font-size: 12px; color: #555; + border: 1px solid #ccc; + border-right: 0; } .quickReply { @@ -158,3 +157,30 @@ color: white; border-color: #075e54; } + +.quickReply:hover::placeholder { + color: white; +} + +.quickReply2 { + width: 100%; + background: #fff; + border: 1px solid #ccc; + padding: 8px 14px; + border-radius: 0 20px 20px 0; + border: 1px solid #ccc; + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; +} + +.quickReply2:hover { + background: #075e54; + color: white; + border-color: #075e54; + border: 1px solid #075e54; +} + +.quickReply2:hover::placeholder { + color: white; +} \ No newline at end of file diff --git a/src/Dashboard.js b/src/Dashboard.js index fac9731..499fad4 100644 --- a/src/Dashboard.js +++ b/src/Dashboard.js @@ -18,6 +18,7 @@ const Dashboard = () => { const [modalContent, setModalContent] = useState(null); const [rawData, setRawData] = useState([]); const [loading, setLoading] = useState(true); // ⬅️ Tambahkan state loading + const [checkOnce, setCheckOnce] = useState(false); // ⬅️ Tambahkan state loading const [stats, setStats] = useState({ totalChats: 0, @@ -124,9 +125,49 @@ const Dashboard = () => { } }; - fetchData(); + if (!checkOnce && 'serviceWorker' in navigator) { + subscribeUser(); + setCheckOnce(false); + } + + fetchData(); // Jalankan langsung saat komponen di-mount + const interval = setInterval(fetchData, 30000); // Jalankan setiap 30 detik + return () => clearInterval(interval); // Bersihkan interval saat komponen unmount + }, [navigate]); + const subscribeUser = async () => { + const registration = await navigator.serviceWorker.register('/sw.js', { + scope: '/', + }); + + const subscription = await registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: urlBase64ToUint8Array('BPT-ypQB0Z7HndmeFhRR7AMjDujCLSbOQ21VoVHLQg9MOfWhEZ7SKH5cMjLqkXHl2sTuxdY2rjHDOAxhRK2G2K4'), + }); + + const token = localStorage.getItem('token'); + + await fetch('https://bot.kediritechnopark.com/webhook/subscribe', { + method: 'POST', + body: JSON.stringify({ + subscription, // ← push subscription object + }), + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + }); + + }; + + function urlBase64ToUint8Array(base64String) { + const padding = '='.repeat((4 - base64String.length % 4) % 4); + const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/'); + const rawData = atob(base64); + return Uint8Array.from([...rawData].map(char => char.charCodeAt(0))); + } + const openConversationsModal = () => { setModalContent(); }; @@ -147,13 +188,13 @@ const Dashboard = () => { const prefixLabelMap = { WEB: 'Web App', - WAP: 'WhatsApp', + TGG: 'Telegram', DME: 'Instagram', }; const prefixColors = { WEB: { border: '#4285F4', background: 'rgba(66, 133, 244, 0.2)' }, - WAP: { border: '#25D366', background: 'rgba(37, 211, 102, 0.2)' }, + TGG: { border: '#25D366', background: 'rgba(37, 211, 102, 0.2)' }, DME: { border: '#AA00FF', background: 'rgba(170, 0, 255, 0.2)' }, }; @@ -225,18 +266,17 @@ const Dashboard = () => {
{isLoggedIn ? ( -
+
{/* ✅ Pindahkan ref ke sini */} {isMenuOpen && (
-
)}
+ ) : ( Login )} diff --git a/src/Dashboard.module.css b/src/Dashboard.module.css index e742a07..52dcf65 100644 --- a/src/Dashboard.module.css +++ b/src/Dashboard.module.css @@ -50,7 +50,7 @@ .statsGrid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-top: 20px; } @@ -202,8 +202,8 @@ position: absolute; } .dropdownToggle { - background-color: #007bff; - color: white; + background-color: #ffff; + color: #255e54; padding: 8px 12px; border: none; border-radius: 4px; diff --git a/src/ResetPassword.js b/src/ResetPassword.js new file mode 100644 index 0000000..8500b3d --- /dev/null +++ b/src/ResetPassword.js @@ -0,0 +1,104 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; // ⬅️ Tambahkan ini +import styles from './Login.module.css'; + +const ResetPassword = () => { + const navigate = useNavigate(); // ⬅️ Gunakan ini untuk navigasi + const [formData, setFormData] = useState({ + oldPassword: '', + newPassword: '' + }); + + const [error, setError] = useState(''); + const [success, setSuccess] = useState(''); + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(''); + setSuccess(''); + + const token = localStorage.getItem('token'); + + if (!token) { + setError('Anda belum login. Silakan login terlebih dahulu.'); + return; + } + + try { + const response = await fetch('https://bot.kediritechnopark.com/webhook/reset-password', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ + password: formData.oldPassword, + newPassword: formData.newPassword + }) + }); + + const data = await response.json(); + if (data?.success) { + setSuccess('Password berhasil diubah'); + setFormData({ oldPassword: '', newPassword: '' }); + } else { + setError(data?.message || 'Gagal mereset password'); + } + } catch (err) { + console.error('Reset Error:', err); + setError('Gagal terhubung ke server'); + } + }; + + return ( +
+
+ Logo +

Ganti Password

+

Masukkan password lama dan yang baru

+
+ + + {error &&

{error}

} + {success &&

{success}

} + +
+ + {/* Tombol kembali */} + + +
+ © 2025 Kloowear AI - Admin Panel +
+
+
+ ); +}; + +export default ResetPassword;