This commit introduces the foundational user-facing chatbot interface. - Adds a new `ChatbotPage` component with a complete UI for displaying messages, user input, and a simulated bot response. - Implements a "typing" indicator for a better user experience. - Integrates `react-router-dom` to handle application routing, directing the root path to the new chatbot and moving the admin login to `/admin`. - Adds `react-icons` as a new dependency for UI elements. - Extends Tailwind CSS theme with new colors and adds custom CSS for the typing animation.
121 lines
5.1 KiB
JavaScript
121 lines
5.1 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { FiCamera, FiSend, FiUser } from 'react-icons/fi';
|
|
|
|
const ChatbotPage = () => {
|
|
const [messages, setMessages] = useState([
|
|
{ id: 1, text: 'Halo Kak! 👋 Selamat datang di Adventure Rental. Saya Maya, asisten virtual yang siap membantu Anda. Ada yang bisa saya bantu?', sender: 'bot', timestamp: '10:30' },
|
|
]);
|
|
const [inputMessage, setInputMessage] = useState('');
|
|
const [isTyping, setIsTyping] = useState(false);
|
|
|
|
const handleSendMessage = () => {
|
|
if (inputMessage.trim() === '') return;
|
|
|
|
const newMessage = {
|
|
id: messages.length + 1,
|
|
text: inputMessage,
|
|
sender: 'user',
|
|
timestamp: new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }),
|
|
};
|
|
setMessages((prevMessages) => [...prevMessages, newMessage]);
|
|
setInputMessage('');
|
|
setIsTyping(true);
|
|
|
|
// Simulate bot response
|
|
setTimeout(() => {
|
|
const botResponse = {
|
|
id: messages.length + 3, // Adjusted for the new message and typing indicator
|
|
text: `Anda mengirim: "${inputMessage}"`,
|
|
sender: 'bot',
|
|
timestamp: new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }),
|
|
};
|
|
setIsTyping(false);
|
|
setMessages((prevMessages) => [...prevMessages, botResponse]);
|
|
}, 2000);
|
|
};
|
|
|
|
const handleKeyPress = (e) => {
|
|
if (e.key === 'Enter') {
|
|
handleSendMessage();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="w-full h-screen flex items-center justify-center bg-login-bg bg-cover bg-center p-4">
|
|
<div className="w-full max-w-md md:max-w-2xl h-full md:h-[95vh] md:max-h-[800px] flex flex-col bg-black/30 backdrop-blur-xl rounded-2xl shadow-2xl overflow-hidden">
|
|
{/* Header */}
|
|
<div className="p-3 flex items-center space-x-3 flex-shrink-0">
|
|
<div className="w-10 h-10 rounded-full bg-brand-orange flex items-center justify-center text-white text-xl font-bold">
|
|
M
|
|
</div>
|
|
<div>
|
|
<h1 className="text-white font-bold">Maya</h1>
|
|
<p className="text-gray-200 text-xs">Adventure Assistant</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Chat Window */}
|
|
<div className="flex-grow p-4 overflow-y-auto bg-white/20">
|
|
{messages.map((message) => (
|
|
<div key={message.id} className={`relative w-full flex ${message.sender === 'user' ? 'justify-end' : 'justify-start'} mb-6`}>
|
|
<div className={`max-w-[calc(100%-3rem)] ${message.sender === 'user' ? 'order-1' : 'order-2'}`}>
|
|
<div className={`p-3 rounded-lg shadow-md ${message.sender === 'user' ? 'bg-brand-orange text-white rounded-br-sm' : 'bg-white text-gray-800 rounded-bl-sm'}`}>
|
|
<p>{message.text}</p>
|
|
<p className={`text-xs mt-1 ${message.sender === 'user' ? 'text-gray-200 text-left' : 'text-gray-500 text-right'}`}>{message.timestamp}</p>
|
|
</div>
|
|
</div>
|
|
{message.sender === 'bot' && (
|
|
<div className="absolute bottom-0 left-2 w-8 h-8 rounded-full bg-brand-orange flex items-center justify-center text-white font-bold border-2 border-white/50 -mb-4">
|
|
M
|
|
</div>
|
|
)}
|
|
{message.sender === 'user' && (
|
|
<div className="absolute bottom-0 right-2 w-8 h-8 rounded-full bg-white flex items-center justify-center text-gray-600 border-2 border-white/50 -mb-4">
|
|
<FiUser size={20} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
{isTyping && (
|
|
<div className="relative w-full flex justify-start mb-6">
|
|
<div className="absolute bottom-0 left-2 w-8 h-8 rounded-full bg-brand-orange flex items-center justify-center text-white font-bold border-2 border-white/50 -mb-4">
|
|
M
|
|
</div>
|
|
<div className="max-w-[calc(100%-3rem)] ml-8">
|
|
<div className="p-3 rounded-lg bg-white text-gray-800 shadow-md">
|
|
<div className="typing-indicator">
|
|
<span></span>
|
|
<span></span>
|
|
<span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Input Area */}
|
|
<div className="p-3 flex items-center space-x-3 flex-shrink-0">
|
|
<div className="flex-grow relative">
|
|
<input
|
|
type="text"
|
|
placeholder="Ketik pesan..."
|
|
className="w-full p-3 rounded-lg bg-white/20 text-white placeholder-gray-200 focus:outline-none focus:ring-2 focus:ring-brand-orange"
|
|
value={inputMessage}
|
|
onChange={(e) => setInputMessage(e.target.value)}
|
|
onKeyPress={handleKeyPress}
|
|
/>
|
|
</div>
|
|
<button
|
|
className="bg-brand-orange text-white w-12 h-12 rounded-lg flex items-center justify-center hover:bg-orange-600 transition duration-300 flex-shrink-0"
|
|
onClick={handleSendMessage}
|
|
>
|
|
<FiSend size={24} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ChatbotPage; |