ok
This commit is contained in:
@@ -2,14 +2,14 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/dermalounge.jpg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
content="Realtime AI Customer Service"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/dermalounge.jpg" />
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
work correctly both with client-side routing and a non-root public URL.
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>React App</title>
|
<title>Dermalounge</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
7
public/sw.js
Normal file
7
public/sw.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
self.addEventListener('push', event => {
|
||||||
|
const data = event.data.json();
|
||||||
|
self.registration.showNotification(data.title, {
|
||||||
|
body: data.body,
|
||||||
|
icon: '/dermalounge.jpg',
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,6 +3,7 @@ import { BrowserRouter, Routes, Route, Navigate, useParams } from 'react-router-
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import Dashboard from './Dashboard';
|
import Dashboard from './Dashboard';
|
||||||
|
import ResetPassword from './ResetPassword'; // ⬅️ import komponen reset
|
||||||
import TenantDashboard from './TenantDashboard';
|
import TenantDashboard from './TenantDashboard';
|
||||||
import ChatBot from './ChatBot';
|
import ChatBot from './ChatBot';
|
||||||
import Login from './Login';
|
import Login from './Login';
|
||||||
@@ -49,6 +50,7 @@ function App() {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<ChatBotWrapper />} />
|
<Route path="/" element={<ChatBotWrapper />} />
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
|
<Route path="/reset-password" element={<ResetPassword />} />
|
||||||
{/* ✅ Route /dashboard diproteksi */}
|
{/* ✅ Route /dashboard diproteksi */}
|
||||||
<Route
|
<Route
|
||||||
path="/dashboard"
|
path="/dashboard"
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ const ChatBot = ({ existingConversation, readOnly, hh }) => {
|
|||||||
|
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
const [isPoppedUp, setIsPoppedUp] = useState(false);
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
const [phoneNumber, setPhoneNumber] = useState('');
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
if (existingConversation && existingConversation.length > 0) {
|
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();
|
const message = textOverride || input.trim();
|
||||||
if (message === '') return;
|
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', {
|
const response = await fetch('https://bot.kediritechnopark.com/webhook/master-agent/ask/dev', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
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();
|
const data = await response.json();
|
||||||
@@ -75,6 +80,12 @@ const ChatBot = ({ existingConversation, readOnly, hh }) => {
|
|||||||
{ sender: 'bot', text: botAnswer, time: getTime() },
|
{ 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);
|
setIsLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(tryCount)
|
console.log(tryCount)
|
||||||
@@ -87,7 +98,7 @@ const ChatBot = ({ existingConversation, readOnly, hh }) => {
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setTimeout(() => sendMessage(message, tryCount + 1), 3000);
|
setTimeout(() => sendMessage(message, name, phoneNumber, tryCount + 1), 3000);
|
||||||
|
|
||||||
console.error('Fetch error:', error);
|
console.error('Fetch error:', error);
|
||||||
}
|
}
|
||||||
@@ -157,12 +168,13 @@ const ChatBot = ({ existingConversation, readOnly, hh }) => {
|
|||||||
onKeyDown={(e) => e.key === 'Enter' && sendMessage()}
|
onKeyDown={(e) => e.key === 'Enter' && sendMessage()}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button onClick={() => sendMessage()} disabled={isLoading}>
|
<button onClick={() => sendMessage()} disabled={isLoading}>
|
||||||
Kirim
|
Kirim
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{isPoppedUp &&
|
||||||
<div className={styles.PopUp}>
|
<div className={styles.PopUp}>
|
||||||
|
|
||||||
<div className={`${styles.message} ${styles['bot']}`}>
|
<div className={`${styles.message} ${styles['bot']}`}>
|
||||||
Untuk bisa membantu Anda lebih jauh, boleh saya tahu nama dan nomor telepon Anda?
|
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 😊
|
Informasi ini juga membantu tim admin kami jika perlu melakukan follow-up nantinya 😊
|
||||||
@@ -171,26 +183,53 @@ const ChatBot = ({ existingConversation, readOnly, hh }) => {
|
|||||||
className={styles.quickReply}
|
className={styles.quickReply}
|
||||||
placeholder="Nama Lengkapmu"
|
placeholder="Nama Lengkapmu"
|
||||||
onFocus={() => console.log('Nama focused')}
|
onFocus={() => console.log('Nama focused')}
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
maxLength={40}
|
||||||
/>
|
/>
|
||||||
<div className={styles.inputGroup}>
|
<div className={styles.inputGroup}>
|
||||||
<span className={styles.prefix}>+62</span>
|
<span className={styles.prefix}>+62</span>
|
||||||
<input
|
<input
|
||||||
type="tel"
|
type="text"
|
||||||
className={styles.quickReply}
|
inputMode="numeric"
|
||||||
|
pattern="[0-9]*"
|
||||||
|
maxLength={11}
|
||||||
|
className={styles.quickReply2}
|
||||||
placeholder="Nomor HP"
|
placeholder="Nomor HP"
|
||||||
|
value={phoneNumber}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
// Hanya angka, maksimal 11 karakter
|
||||||
|
if (/^\d{0,11}$/.test(value)) {
|
||||||
|
setPhoneNumber(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
onFocus={() => console.log('Telepon focused')}
|
onFocus={() => console.log('Telepon focused')}
|
||||||
style={{border: 0, width: '100%'}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={styles.quickReply}
|
className={styles.quickReply}
|
||||||
|
style={{ color: name.length > 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
|
Lanjut
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -129,18 +129,17 @@
|
|||||||
.inputGroup {
|
.inputGroup {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 20px;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prefix {
|
.prefix {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
border-right: 1px solid #ccc;
|
border-radius: 20px 0px 0px 20px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #555;
|
color: #555;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quickReply {
|
.quickReply {
|
||||||
@@ -158,3 +157,30 @@
|
|||||||
color: white;
|
color: white;
|
||||||
border-color: #075e54;
|
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;
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ const Dashboard = () => {
|
|||||||
const [modalContent, setModalContent] = useState(null);
|
const [modalContent, setModalContent] = useState(null);
|
||||||
const [rawData, setRawData] = useState([]);
|
const [rawData, setRawData] = useState([]);
|
||||||
const [loading, setLoading] = useState(true); // ⬅️ Tambahkan state loading
|
const [loading, setLoading] = useState(true); // ⬅️ Tambahkan state loading
|
||||||
|
const [checkOnce, setCheckOnce] = useState(false); // ⬅️ Tambahkan state loading
|
||||||
|
|
||||||
const [stats, setStats] = useState({
|
const [stats, setStats] = useState({
|
||||||
totalChats: 0,
|
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]);
|
}, [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 = () => {
|
const openConversationsModal = () => {
|
||||||
setModalContent(<Conversations conversations={conversations} />);
|
setModalContent(<Conversations conversations={conversations} />);
|
||||||
};
|
};
|
||||||
@@ -147,13 +188,13 @@ const Dashboard = () => {
|
|||||||
|
|
||||||
const prefixLabelMap = {
|
const prefixLabelMap = {
|
||||||
WEB: 'Web App',
|
WEB: 'Web App',
|
||||||
WAP: 'WhatsApp',
|
TGG: 'Telegram',
|
||||||
DME: 'Instagram',
|
DME: 'Instagram',
|
||||||
};
|
};
|
||||||
|
|
||||||
const prefixColors = {
|
const prefixColors = {
|
||||||
WEB: { border: '#4285F4', background: 'rgba(66, 133, 244, 0.2)' },
|
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)' },
|
DME: { border: '#AA00FF', background: 'rgba(170, 0, 255, 0.2)' },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -225,18 +266,17 @@ const Dashboard = () => {
|
|||||||
<div className={styles.dashboardHeader}>
|
<div className={styles.dashboardHeader}>
|
||||||
{isLoggedIn ? (
|
{isLoggedIn ? (
|
||||||
|
|
||||||
<div className={styles.dropdownContainer}>
|
<div className={styles.dropdownContainer} ref={menuRef}> {/* ✅ Pindahkan ref ke sini */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||||
className={styles.dropdownToggle}
|
className={styles.dropdownToggle}
|
||||||
ref={menuRef}
|
|
||||||
>
|
>
|
||||||
☰ Menu
|
☰ Menu
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{isMenuOpen && (
|
{isMenuOpen && (
|
||||||
<div className={styles.dropdownMenu}>
|
<div className={styles.dropdownMenu}>
|
||||||
<button onClick={handleLogout} className={styles.dropdownItem}>
|
<button onClick={() => navigate('/reset-password')} className={styles.dropdownItem}>
|
||||||
Ganti Password
|
Ganti Password
|
||||||
</button>
|
</button>
|
||||||
<button onClick={handleLogout} className={styles.dropdownItem}>
|
<button onClick={handleLogout} className={styles.dropdownItem}>
|
||||||
@@ -245,6 +285,7 @@ const Dashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
<a href="/login" className={styles.loginButton}>Login</a>
|
<a href="/login" className={styles.loginButton}>Login</a>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
|
|
||||||
.statsGrid {
|
.statsGrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
@@ -202,8 +202,8 @@ position: absolute;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropdownToggle {
|
.dropdownToggle {
|
||||||
background-color: #007bff;
|
background-color: #ffff;
|
||||||
color: white;
|
color: #255e54;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|||||||
104
src/ResetPassword.js
Normal file
104
src/ResetPassword.js
Normal file
@@ -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 (
|
||||||
|
<div className={styles.loginContainer}>
|
||||||
|
<div className={styles.loginBox}>
|
||||||
|
<img src="/dermalounge.jpg" alt="Logo" className={styles.logo} />
|
||||||
|
<h1 className={styles.h1}>Ganti Password</h1>
|
||||||
|
<p className={styles.subtitle}>Masukkan password lama dan yang baru</p>
|
||||||
|
<form onSubmit={handleSubmit} className={styles.form}>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="oldPassword"
|
||||||
|
placeholder="Password Lama"
|
||||||
|
value={formData.oldPassword}
|
||||||
|
onChange={handleChange}
|
||||||
|
className={styles.input}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="newPassword"
|
||||||
|
placeholder="Password Baru"
|
||||||
|
value={formData.newPassword}
|
||||||
|
onChange={handleChange}
|
||||||
|
className={styles.input}
|
||||||
|
/>
|
||||||
|
{error && <p className={styles.error}>{error}</p>}
|
||||||
|
{success && <p style={{ color: 'green', marginBottom: '10px' }}>{success}</p>}
|
||||||
|
<button type="submit" className={styles.button}>
|
||||||
|
Simpan Password Baru
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Tombol kembali */}
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/dashboard')}
|
||||||
|
className={styles.button}
|
||||||
|
style={{ marginTop: '12px', backgroundColor: '#777' }}
|
||||||
|
>
|
||||||
|
Kembali ke Dashboard
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className={styles.footer}>
|
||||||
|
© 2025 Kloowear AI - Admin Panel
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResetPassword;
|
||||||
Reference in New Issue
Block a user