From 82dea0cc7e618b89ac0611fc7d7011356ebc4eec Mon Sep 17 00:00:00 2001 From: Motunrayo Adeneye <67984447+motuncoded@users.noreply.github.com> Date: Sun, 10 Aug 2025 21:13:14 +0100 Subject: [PATCH] feature/imporve accessibiblity --- frontend/README.md | 22 +- frontend/eslint.config.js | 20 +- frontend/src/App.module.css | 10 +- frontend/src/App.tsx | 54 +-- frontend/src/components/Chat.module.css | 29 +- frontend/src/components/Chat.tsx | 324 +++++++++++------- frontend/src/components/Sidebar.module.css | 14 +- frontend/src/components/Sidebar.tsx | 91 +++-- .../src/components/TypingIndicator.module.css | 6 +- frontend/src/components/TypingIndicator.tsx | 19 +- frontend/src/index.css | 12 +- frontend/src/main.tsx | 14 +- frontend/vite.config.ts | 10 +- 13 files changed, 393 insertions(+), 232 deletions(-) diff --git a/frontend/README.md b/frontend/README.md index 7959ce4..10e1545 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -13,9 +13,9 @@ If you are developing a production application, we recommend updating the config ```js export default tseslint.config([ - globalIgnores(['dist']), + globalIgnores(["dist"]), { - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], extends: [ // Other configs... @@ -30,40 +30,40 @@ export default tseslint.config([ ], languageOptions: { parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], + project: ["./tsconfig.node.json", "./tsconfig.app.json"], tsconfigRootDir: import.meta.dirname, }, // other options... }, }, -]) +]); ``` You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: ```js // eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' +import reactX from "eslint-plugin-react-x"; +import reactDom from "eslint-plugin-react-dom"; export default tseslint.config([ - globalIgnores(['dist']), + globalIgnores(["dist"]), { - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], extends: [ // Other configs... // Enable lint rules for React - reactX.configs['recommended-typescript'], + reactX.configs["recommended-typescript"], // Enable lint rules for React DOM reactDom.configs.recommended, ], languageOptions: { parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], + project: ["./tsconfig.node.json", "./tsconfig.app.json"], tsconfigRootDir: import.meta.dirname, }, // other options... }, }, -]) +]); ``` diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index d94e7de..f461674 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,18 +1,18 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' -import { globalIgnores } from 'eslint/config' +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; +import { globalIgnores } from "eslint/config"; export default tseslint.config([ - globalIgnores(['dist']), + globalIgnores(["dist"]), { - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], extends: [ js.configs.recommended, tseslint.configs.recommended, - reactHooks.configs['recommended-latest'], + reactHooks.configs["recommended-latest"], reactRefresh.configs.vite, ], languageOptions: { @@ -20,4 +20,4 @@ export default tseslint.config([ globals: globals.browser, }, }, -]) +]); diff --git a/frontend/src/App.module.css b/frontend/src/App.module.css index 4b658f0..d7aae00 100644 --- a/frontend/src/App.module.css +++ b/frontend/src/App.module.css @@ -3,7 +3,7 @@ height: 100%; max-width: 1200px; max-height: 800px; - background: #FFFFFD; + background: #fffffd; display: flex; border-radius: 12px; overflow: hidden; @@ -60,12 +60,14 @@ main { margin-bottom: 0.75rem; cursor: pointer; border: 1px solid #e5e7eb; - transition: background-color 0.2s, box-shadow 0.2s; + transition: + background-color 0.2s, + box-shadow 0.2s; } .historyItem:hover { background-color: #f9fafb; - box-shadow: 0 2px 4px rgba(0,0,0,0.05); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); } .historyItemContent { @@ -94,4 +96,4 @@ main { font-size: 0.875rem; color: #6b7280; margin-left: 1rem; -} \ No newline at end of file +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e2e1726..ad333e4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,18 +1,27 @@ -import Chat from './components/Chat'; -import Sidebar from './components/Sidebar'; -import styles from './App.module.css'; -import { useState } from 'react'; +import Chat from "./components/Chat"; +import Sidebar from "./components/Sidebar"; +import styles from "./App.module.css"; +import { useState } from "react"; const saveSessionToHistory = () => { - const history = JSON.parse(localStorage.getItem('apiconf_chat_history') || '[]'); - const sessionId = localStorage.getItem('apiconf_session_id'); - + const history = JSON.parse( + localStorage.getItem("apiconf_chat_history") || "[]", + ); + const sessionId = localStorage.getItem("apiconf_session_id"); + // Avoid adding duplicate or empty sessions - if (sessionId && !history.some((item: { sessionId: string }) => item.sessionId === sessionId)) { + if ( + sessionId && + !history.some((item: { sessionId: string }) => item.sessionId === sessionId) + ) { const timestamp = new Date().toLocaleString(); - const preview = localStorage.getItem(`session_preview_${sessionId}`) || 'New Chat'; + const preview = + localStorage.getItem(`session_preview_${sessionId}`) || "New Chat"; history.unshift({ sessionId, timestamp, preview }); - localStorage.setItem('apiconf_chat_history', JSON.stringify(history.slice(0, 20))); // Increased limit + localStorage.setItem( + "apiconf_chat_history", + JSON.stringify(history.slice(0, 20)), + ); // Increased limit } }; @@ -20,7 +29,7 @@ const SettingsComponent = () =>
Settings view coming soon.
; const App: React.FC = () => { const [sidebarOpen, setSidebarOpen] = useState(false); - const [view, setView] = useState<'chat' | 'settings'>('chat'); // Simplified view + const [view, setView] = useState<"chat" | "settings">("chat"); // Simplified view const [resetSignal, setResetSignal] = useState(0); const toggleSidebar = () => { @@ -32,23 +41,24 @@ const App: React.FC = () => { }; // Get active session ID from local storage to pass to sidebar - const activeSessionId = localStorage.getItem('apiconf_session_id'); + const activeSessionId = localStorage.getItem("apiconf_session_id"); const handleNewChat = () => { saveSessionToHistory(); - const newSessionId = Math.random().toString(36).substring(2, 15) + Date.now().toString(36); - localStorage.setItem('apiconf_session_id', newSessionId); - setView('chat'); + const newSessionId = + Math.random().toString(36).substring(2, 15) + Date.now().toString(36); + localStorage.setItem("apiconf_session_id", newSessionId); + setView("chat"); if (window.innerWidth <= 768) setSidebarOpen(false); // Close sidebar on mobile - setResetSignal(s => s + 1); + setResetSignal((s) => s + 1); }; const handleRestoreSession = (sessionId: string) => { saveSessionToHistory(); // Save the current session before restoring another - localStorage.setItem('apiconf_session_id', sessionId); - setView('chat'); + localStorage.setItem("apiconf_session_id", sessionId); + setView("chat"); if (window.innerWidth <= 768) setSidebarOpen(false); // Close sidebar on mobile - setResetSignal(s => s + 1); + setResetSignal((s) => s + 1); }; return ( @@ -61,8 +71,10 @@ const App: React.FC = () => { activeSessionId={activeSessionId} />
- {view === 'chat' && } - {view === 'settings' && } + {view === "chat" && ( + + )} + {view === "settings" && }
); diff --git a/frontend/src/components/Chat.module.css b/frontend/src/components/Chat.module.css index daf55d5..e88da44 100644 --- a/frontend/src/components/Chat.module.css +++ b/frontend/src/components/Chat.module.css @@ -13,8 +13,12 @@ font-weight: 600; color: #1e293b; flex-shrink: 0; -} + +h1 { + font-size: clamp(1rem, 4vw, 1.15rem); +} +} .content { flex: 1; display: flex; @@ -163,7 +167,9 @@ border-radius: 12px; border: 1px solid #e2e8f0; } - +.inputForm:focus-within{ + outline:1px solid #0000ff; +} .inputField { flex: 1; padding: 12px; @@ -204,6 +210,17 @@ cursor: pointer; margin-right: 16px; } +.srOnly { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} @media (max-width: 768px) { .chat { @@ -213,8 +230,10 @@ } .chatHeader { - padding: 16px 12px; - font-size: 16px; + display: flex; + align-items: center; + + } .content { @@ -241,6 +260,4 @@ .menuButton { display: block; } - } - diff --git a/frontend/src/components/Chat.tsx b/frontend/src/components/Chat.tsx index 66c92b9..c040791 100644 --- a/frontend/src/components/Chat.tsx +++ b/frontend/src/components/Chat.tsx @@ -1,14 +1,14 @@ -import { FiMenu, FiSend } from 'react-icons/fi'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { FiMenu, FiSend } from "react-icons/fi"; +import React, { useCallback, useEffect, useRef, useState } from "react"; -import ReactMarkdown from 'react-markdown'; -import TypingIndicator from './TypingIndicator'; -import remarkGfm from 'remark-gfm'; -import styles from './Chat.module.css'; +import ReactMarkdown from "react-markdown"; +import TypingIndicator from "./TypingIndicator"; +import remarkGfm from "remark-gfm"; +import styles from "./Chat.module.css"; interface Message { text: string; - sender: 'user' | 'bot'; + sender: "user" | "bot"; timestamp: string; } @@ -19,107 +19,128 @@ interface ChatProps { const Chat: React.FC = ({ onMenuClick, resetSignal }) => { const [messages, setMessages] = useState([]); - const [input, setInput] = useState(''); + const [input, setInput] = useState(""); const [isTyping, setIsTyping] = useState(false); - const [processedMessages, setProcessedMessages] = useState>(new Set()); + const [processedMessages, setProcessedMessages] = useState>( + new Set(), + ); const [hasProcessedUrlMessage, setHasProcessedUrlMessage] = useState(false); const [error, setError] = useState(null); const getOrCreateId = (key: string) => { let id = localStorage.getItem(key); if (!id) { - id = Math.random().toString(36).substring(2, 15) + Date.now().toString(36); + id = + Math.random().toString(36).substring(2, 15) + Date.now().toString(36); localStorage.setItem(key, id); } return id; }; - const userId = getOrCreateId('apiconf_user_id'); - const sessionId = getOrCreateId('apiconf_session_id'); + const userId = getOrCreateId("apiconf_user_id"); + const sessionId = getOrCreateId("apiconf_session_id"); - const handleSend = useCallback(async (messageToSend: string) => { - if (!messageToSend.trim()) return; + const handleSend = useCallback( + async (messageToSend: string) => { + if (!messageToSend.trim()) return; - // Save the first user message as the preview for the history - const isFirstUserMessage = messages.filter(m => m.sender === 'user').length === 0; - if (isFirstUserMessage) { - localStorage.setItem(`session_preview_${sessionId}`, messageToSend); - } + // Save the first user message as the preview for the history + const isFirstUserMessage = + messages.filter((m) => m.sender === "user").length === 0; + if (isFirstUserMessage) { + localStorage.setItem(`session_preview_${sessionId}`, messageToSend); + } - if (processedMessages.has(messageToSend)) { - console.log('Message already processed:', messageToSend); - return; - } + if (processedMessages.has(messageToSend)) { + console.log("Message already processed:", messageToSend); + return; + } + + console.log("Sending message to /api/v1/agents/chat:", messageToSend); - console.log('Sending message to /api/v1/agents/chat:', messageToSend); - - const userMessage: Message = { - text: messageToSend, - sender: 'user', - timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - }; - - setMessages((prevMessages) => [...prevMessages, userMessage]); - setIsTyping(true); - setError(null); - - try { - const response = await fetch('/api/v1/agents/chat', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - message: messageToSend, - user_id: userId, - session_id: sessionId, + const userMessage: Message = { + text: messageToSend, + sender: "user", + timestamp: new Date().toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", }), - }); + }; - if (!response.ok) { - throw new Error(`Network response was not ok: ${response.status}`); - } + setMessages((prevMessages) => [...prevMessages, userMessage]); + setIsTyping(true); + setError(null); - const result = await response.json(); - console.log('API response:', result); + try { + const response = await fetch("/api/v1/agents/chat", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + message: messageToSend, + user_id: userId, + session_id: sessionId, + }), + }); - const botMessage: Message = { - text: result.data.response, - sender: 'bot', - timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - }; + if (!response.ok) { + throw new Error(`Network response was not ok: ${response.status}`); + } - setMessages((prevMessages) => [...prevMessages, botMessage]); - setProcessedMessages((prev) => new Set(prev).add(messageToSend)); + const result = await response.json(); + console.log("API response:", result); - // Clear URL parameters after processing - if (window.location.search) { - window.history.replaceState({}, document.title, window.location.pathname); - } + const botMessage: Message = { + text: result.data.response, + sender: "bot", + timestamp: new Date().toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }), + }; - } catch (error) { - console.error('Error fetching chat response:', error); - setError('Sorry, I seem to be having trouble connecting. Please try again later.'); - const errorMessage: Message = { - text: 'Sorry, I seem to be having trouble connecting. Please try again later.', - sender: 'bot', - timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - }; - setMessages((prevMessages) => [...prevMessages, errorMessage]); - } finally { - setIsTyping(false); - } - }, [processedMessages, userId, sessionId, messages]); + setMessages((prevMessages) => [...prevMessages, botMessage]); + setProcessedMessages((prev) => new Set(prev).add(messageToSend)); + + // Clear URL parameters after processing + if (window.location.search) { + window.history.replaceState( + {}, + document.title, + window.location.pathname, + ); + } + } catch (error) { + console.error("Error fetching chat response:", error); + setError( + "Sorry, I seem to be having trouble connecting. Please try again later.", + ); + const errorMessage: Message = { + text: "Sorry, I seem to be having trouble connecting. Please try again later.", + sender: "bot", + timestamp: new Date().toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }), + }; + setMessages((prevMessages) => [...prevMessages, errorMessage]); + } finally { + setIsTyping(false); + } + }, + [processedMessages, userId, sessionId, messages], + ); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); handleSend(input); - setInput(''); + setInput(""); }; const messagesEndRef = useRef(null); const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }; useEffect(() => { @@ -130,31 +151,31 @@ const Chat: React.FC = ({ onMenuClick, resetSignal }) => { useEffect(() => { if (hasProcessedUrlMessage) return; - console.log('=== URL DEBUGGING ==='); - console.log('Current URL:', window.location.href); - console.log('Search params:', window.location.search); - console.log('Pathname:', window.location.pathname); + console.log("=== URL DEBUGGING ==="); + console.log("Current URL:", window.location.href); + console.log("Search params:", window.location.search); + console.log("Pathname:", window.location.pathname); // Use native URLSearchParams to ensure we get the URL parameter const urlParams = new URLSearchParams(window.location.search); - const messageFromUrl = urlParams.get('message'); + const messageFromUrl = urlParams.get("message"); - console.log('All URL params:', Object.fromEntries(urlParams)); - console.log('Message from URL:', messageFromUrl); + console.log("All URL params:", Object.fromEntries(urlParams)); + console.log("Message from URL:", messageFromUrl); if (messageFromUrl) { const decodedMessage = decodeURIComponent(messageFromUrl); - console.log('Decoded message:', decodedMessage); - console.log('About to send message to API...'); - + console.log("Decoded message:", decodedMessage); + console.log("About to send message to API..."); + setHasProcessedUrlMessage(true); - + // Add a small delay to ensure the component is fully mounted setTimeout(() => { handleSend(decodedMessage); }, 100); } else { - console.log('No message parameter found in URL'); + console.log("No message parameter found in URL"); } }, [handleSend, hasProcessedUrlMessage]); @@ -168,27 +189,59 @@ const Chat: React.FC = ({ onMenuClick, resetSignal }) => { }, [resetSignal]); return ( -
+
- - Chat with Ndu +

Chat with Ndu

-
-
+
+
{messages.length === 0 ? (
-

Welcome!

-

Your friendly assistant for the API Conference 2025 in Lagos. Ask me about speakers, schedules, and more!

+

Welcome!

+

+ Your friendly assistant for the API Conference 2025 in Lagos. + Ask me about speakers, schedules, and more! +

- - -
@@ -198,11 +251,13 @@ const Chat: React.FC = ({ onMenuClick, resetSignal }) => {
- {msg.sender === 'bot' ? ( + {msg.sender === "bot" ? ( {msg.text} @@ -210,36 +265,61 @@ const Chat: React.FC = ({ onMenuClick, resetSignal }) => { msg.text )}
-
{msg.timestamp}
+
)) )} {isTyping && ( -
- +
+
)} - {error &&
{error}
} -
+ {error && ( +

+ {error} +

+ )} + -
-
-
- setInput(e.target.value)} - placeholder="Ask something..." - /> - +
+
+ +
+ + setInput(e.target.value)} + placeholder="Ask something..." + aria-required="true" + aria-describedby="input-instructions" + /> + +
-
+
+ Press Enter to send, or Ctrl+Enter for quick send +
+
); }; -export default Chat; \ No newline at end of file +export default Chat; diff --git a/frontend/src/components/Sidebar.module.css b/frontend/src/components/Sidebar.module.css index 62875cc..46b3c26 100644 --- a/frontend/src/components/Sidebar.module.css +++ b/frontend/src/components/Sidebar.module.css @@ -61,7 +61,9 @@ align-items: center; gap: 12px; font-size: 16px; - transition: background-color 0.2s, color 0.2s; + transition: + background-color 0.2s, + color 0.2s; } .nav a:hover { @@ -119,7 +121,9 @@ font-size: 0.75rem; color: #6b7280; } - +.closeButton { + display: none; +} @media (max-width: 768px) { .sidebar { position: absolute; @@ -157,7 +161,7 @@ font-size: 24px; cursor: pointer; } - + .logo { position: relative; } @@ -166,7 +170,3 @@ .overlay { display: none; } - -.closeButton { - display: none; -} \ No newline at end of file diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 7feb23d..0f07fe8 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from 'react'; -import styles from './Sidebar.module.css'; -import { FiPlus, FiX } from 'react-icons/fi'; +import React, { useEffect, useState } from "react"; +import styles from "./Sidebar.module.css"; +import { FiPlus, FiX } from "react-icons/fi"; interface HistoryItem { sessionId: string; @@ -16,53 +16,92 @@ interface SidebarProps { activeSessionId: string | null; } -const Sidebar: React.FC = ({ isOpen, onClose, onNewChat, onRestoreSession, activeSessionId }) => { +const Sidebar: React.FC = ({ + isOpen, + onClose, + onNewChat, + onRestoreSession, + activeSessionId, +}) => { const [history, setHistory] = useState([]); useEffect(() => { // Refresh the history list whenever the sidebar is opened or the active session changes. if (isOpen) { - setHistory(JSON.parse(localStorage.getItem('apiconf_chat_history') || '[]')); + setHistory( + JSON.parse(localStorage.getItem("apiconf_chat_history") || "[]"), + ); } }, [isOpen, activeSessionId]); return ( <> - {isOpen &&
} + {isOpen && ( + + )} ); }; -export default Sidebar; \ No newline at end of file +export default Sidebar; diff --git a/frontend/src/components/TypingIndicator.module.css b/frontend/src/components/TypingIndicator.module.css index 749ba68..e4e74d0 100644 --- a/frontend/src/components/TypingIndicator.module.css +++ b/frontend/src/components/TypingIndicator.module.css @@ -22,10 +22,12 @@ } @keyframes bounce { - 0%, 80%, 100% { + 0%, + 80%, + 100% { transform: scale(0); } 40% { transform: scale(1); } -} \ No newline at end of file +} diff --git a/frontend/src/components/TypingIndicator.tsx b/frontend/src/components/TypingIndicator.tsx index 17b35ef..3f65f0e 100644 --- a/frontend/src/components/TypingIndicator.tsx +++ b/frontend/src/components/TypingIndicator.tsx @@ -1,14 +1,19 @@ -import React from 'react'; -import styles from './TypingIndicator.module.css'; +import React from "react"; +import styles from "./TypingIndicator.module.css"; const TypingIndicator: React.FC = () => { return ( -
-
-
-
+
+ + +
); }; -export default TypingIndicator; \ No newline at end of file +export default TypingIndicator; diff --git a/frontend/src/index.css b/frontend/src/index.css index 0f04501..93b033f 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,9 +1,9 @@ /* frontend/src/index.css */ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"); :root { - font-family: 'Inter', sans-serif; + font-family: "Inter", sans-serif; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; @@ -17,7 +17,8 @@ padding: 0; } -html, body { +html, +body { height: 100%; overflow: hidden; } @@ -31,10 +32,13 @@ body { background-color: #f0f0f0; } +button:focus{ + outline:1px solid blue; +} #root { width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; -} \ No newline at end of file +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 31cc4b6..69ccb3e 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,14 +1,14 @@ -import './index.css'; +import "./index.css"; -import App from './App.tsx'; -import { BrowserRouter } from 'react-router-dom'; -import { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; +import App from "./App.tsx"; +import { BrowserRouter } from "react-router-dom"; +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; -createRoot(document.getElementById('root')!).render( +createRoot(document.getElementById("root")!).render( - + , ); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 8377546..8d49c65 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,15 +1,15 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; // https://vite.dev/config/ export default defineConfig({ plugins: [react()], server: { proxy: { - '/api': { - target: 'http://127.0.0.1:8000', + "/api": { + target: "http://127.0.0.1:8000", changeOrigin: true, }, }, }, -}) +});