diff --git a/webapp/chat-app/package.json b/webapp/chat-app/package.json index 99d2e86e..1ca8f261 100644 --- a/webapp/chat-app/package.json +++ b/webapp/chat-app/package.json @@ -3,6 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { + "@fortawesome/free-solid-svg-icons": "^6.7.1", + "@fortawesome/react-fontawesome": "^0.2.2", "@reduxjs/toolkit": "^1.9.7", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", @@ -24,9 +26,6 @@ "@types/react-router-dom": "^5.3.3", "@types/styled-components": "^5.1.34", "eslint": "^8.0.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.8", "typescript": "^4.9.5" }, "scripts": { @@ -34,6 +33,7 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", + "lint": "eslint \"src/**/*.{ts,tsx,js,jsx}\" --fix", "type-check": "tsc --noEmit" }, "eslintConfig": { diff --git a/webapp/chat-app/src/App.tsx b/webapp/chat-app/src/App.tsx index f26c22f3..68c60200 100644 --- a/webapp/chat-app/src/App.tsx +++ b/webapp/chat-app/src/App.tsx @@ -4,9 +4,8 @@ import {store} from './store'; import websocket from './services/websocket'; import {GlobalStyles} from './styles/GlobalStyles'; import ChatInterface from './components/ChatInterface'; -import Header from "./components/Header"; -import {setTheme} from "./store/slices/uiSlice"; import ThemeProvider from './themes/ThemeProvider'; +import {Menu} from "./components/Menu/Menu"; const App: React.FC = () => { const sessionId = websocket.getSessionId(); @@ -16,7 +15,7 @@ const App: React.FC = () => {
-
+ { ); }; -export default App; \ No newline at end of file +export default App; diff --git a/webapp/chat-app/src/components/ChatInterface.tsx b/webapp/chat-app/src/components/ChatInterface.tsx index 2de38f7b..0d5d83ac 100644 --- a/webapp/chat-app/src/components/ChatInterface.tsx +++ b/webapp/chat-app/src/components/ChatInterface.tsx @@ -5,8 +5,7 @@ import {useWebSocket} from '../hooks/useWebSocket'; import {addMessage} from '../store/slices/messageSlice'; import MessageList from './MessageList'; import InputArea from './InputArea'; -import Header from './Header'; -import websocket from "@services/websocket"; +import websocket from '@services/websocket'; interface ChatInterfaceProps { sessionId?: string; @@ -20,7 +19,11 @@ const ChatContainer = styled.div` height: 100vh; `; -const ChatInterface: React.FC = ({sessionId: propSessionId, websocket, isConnected}) => { +const ChatInterface: React.FC = ({ + sessionId: propSessionId, + websocket, + isConnected, + }) => { const [sessionId] = useState(() => propSessionId || window.location.hash.slice(1) || 'new'); const dispatch = useDispatch(); const ws = useWebSocket(sessionId); @@ -28,14 +31,17 @@ const ChatInterface: React.FC = ({sessionId: propSessionId, useEffect(() => { const handleMessage = (data: string) => { const [id, version, content] = data.split(','); - - dispatch(addMessage({ - id, - content: content, - version, - type: id.startsWith('u') ? 'user' : 'response', - timestamp: Date.now() - })); + const timestamp = Date.now(); + + dispatch( + addMessage({ + id: `${id}-${timestamp}`, // Make IDs more unique + content: content, + version, + type: id.startsWith('u') ? 'user' : 'response', + timestamp, + }) + ); }; websocket.addMessageHandler(handleMessage); @@ -44,8 +50,6 @@ const ChatInterface: React.FC = ({sessionId: propSessionId, return ( -
{ - }}/> ws.send(msg)}/> diff --git a/webapp/chat-app/src/components/Header.tsx b/webapp/chat-app/src/components/Header.tsx index dfc9fb2a..5ea39535 100644 --- a/webapp/chat-app/src/components/Header.tsx +++ b/webapp/chat-app/src/components/Header.tsx @@ -6,13 +6,13 @@ import {ThemeName} from '../types'; const HeaderContainer = styled.header` background-color: ${({theme}) => theme.colors.surface}; padding: 1rem; - border-bottom: 1px solid ${props => props.theme.colors.border}; + border-bottom: 1px solid ${(props) => props.theme.colors.border}; `; const ThemeSelect = styled.select` padding: 0.5rem; - border-radius: ${props => props.theme.sizing.borderRadius.sm}; - border: 1px solid ${props => props.theme.colors.border}; + border-radius: ${(props) => props.theme.sizing.borderRadius.sm}; + border: 1px solid ${(props) => props.theme.colors.border}; `; interface HeaderProps { @@ -24,10 +24,7 @@ const Header: React.FC = ({onThemeChange}) => { return ( - setTheme(e.target.value as any)} - > + setTheme(e.target.value as any)}> diff --git a/webapp/chat-app/src/components/InputArea.tsx b/webapp/chat-app/src/components/InputArea.tsx index 3bffd3bd..901c0b0a 100644 --- a/webapp/chat-app/src/components/InputArea.tsx +++ b/webapp/chat-app/src/components/InputArea.tsx @@ -3,15 +3,15 @@ import styled from 'styled-components'; const InputContainer = styled.div` padding: 1rem; - background-color: ${props => props.theme.colors.surface}; - border-top: 1px solid ${props => props.theme.colors.border}; + background-color: ${(props) => props.theme.colors.surface}; + border-top: 1px solid ${(props) => props.theme.colors.border}; `; const TextArea = styled.textarea` width: 100%; padding: 0.5rem; - border-radius: ${props => props.theme.sizing.borderRadius.md}; - border: 1px solid ${props => props.theme.colors.border}; + border-radius: ${(props) => props.theme.sizing.borderRadius.md}; + border: 1px solid ${(props) => props.theme.colors.border}; font-family: inherit; resize: vertical; `; diff --git a/webapp/chat-app/src/components/Menu/Menu.tsx b/webapp/chat-app/src/components/Menu/Menu.tsx new file mode 100644 index 00000000..146ed280 --- /dev/null +++ b/webapp/chat-app/src/components/Menu/Menu.tsx @@ -0,0 +1,135 @@ +import React from 'react'; +import styled from 'styled-components'; +import {useDispatch} from 'react-redux'; +import {useTheme} from '../../hooks/useTheme'; +import {showModal} from '../../store/slices/uiSlice'; +import {ThemeName} from '../../themes/themes'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faCog, faHome, faSignInAlt, faSignOutAlt} from '@fortawesome/free-solid-svg-icons'; +import {ThemeMenu} from "./ThemeMenu"; + +const MenuContainer = styled.div` + display: flex; + justify-content: space-between; + padding: ${({theme}) => theme.sizing.spacing.sm}; + background-color: ${({theme}) => theme.colors.surface}; + border-bottom: 1px solid ${({theme}) => theme.colors.border}; +`; + +const ToolbarLeft = styled.div` + display: flex; + gap: ${({theme}) => theme.sizing.spacing.md}; +`; + +const Dropdown = styled.div` + position: relative; + display: inline-block; + + &:hover .dropdown-content { + display: block; + } +`; + +const DropButton = styled.a` + color: ${({theme}) => theme.colors.text.primary}; + padding: ${({theme}) => theme.sizing.spacing.sm}; + text-decoration: none; + cursor: pointer; + + &:hover { + background-color: ${({theme}) => theme.colors.primary}; + color: white; + } +`; + +const DropdownContent = styled.div` + display: none; + position: absolute; + background-color: ${({theme}) => theme.colors.surface}; + min-width: 160px; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); + z-index: 1; +`; + +const DropdownItem = styled.a` + color: ${({theme}) => theme.colors.text.primary}; + padding: ${({theme}) => theme.sizing.spacing.sm}; + text-decoration: none; + display: block; + cursor: pointer; + + &:hover { + background-color: ${({theme}) => theme.colors.primary}; + color: white; + } +`; + +export const Menu: React.FC = () => { + const dispatch = useDispatch(); + const [, setTheme] = useTheme(); + + const handleThemeChange = (theme: ThemeName) => { + setTheme(theme); + }; + + const handleModalOpen = (modalType: string) => { + dispatch(showModal(modalType)); + }; + + return ( + + + + Home + + + + App + + handleModalOpen('sessions')}>Session List + New + + + + + + Session + + + handleModalOpen('settings')}>Settings + handleModalOpen('files')}>Files + handleModalOpen('usage')}>Usage + handleModalOpen('threads')}>Threads + handleModalOpen('share')}>Share + handleModalOpen('cancel')}>Cancel + handleModalOpen('delete')}>Delete + handleModalOpen('verbose')}>Show Verbose + + + + + + + About + + handleModalOpen('privacy')}>Privacy Policy + handleModalOpen('tos')}>Terms of Service + + + + + + + Login + + + handleModalOpen('user-settings')}>Settings + handleModalOpen('user-usage')}>Usage + + Logout + + + + + ); +}; \ No newline at end of file diff --git a/webapp/chat-app/src/components/Menu/ThemeMenu.tsx b/webapp/chat-app/src/components/Menu/ThemeMenu.tsx new file mode 100644 index 00000000..f76e1742 --- /dev/null +++ b/webapp/chat-app/src/components/Menu/ThemeMenu.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import styled from 'styled-components'; +import {useTheme} from '../../hooks/useTheme'; +import {themes} from '../../themes/themes'; + +const ThemeMenuContainer = styled.div` + position: relative; + display: inline-block; +`; + +const ThemeButton = styled.button` + padding: ${({theme}) => theme.sizing.spacing.sm}; + color: ${({theme}) => theme.colors.text.primary}; + background: ${({theme}) => theme.colors.surface}; + border: 1px solid ${({theme}) => theme.colors.border}; + border-radius: ${({theme}) => theme.sizing.borderRadius.sm}; + + &:hover { + background: ${({theme}) => theme.colors.primary}; + color: ${({theme}) => theme.colors.background}; + } +`; + +const ThemeList = styled.div` + position: absolute; + top: 100%; + right: 0; + background: ${({theme}) => theme.colors.surface}; + border: 1px solid ${({theme}) => theme.colors.border}; + border-radius: ${({theme}) => theme.sizing.borderRadius.sm}; + padding: ${({theme}) => theme.sizing.spacing.xs}; + z-index: 10; + min-width: 150px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +`; + +const ThemeOption = styled.button` + width: 100%; + padding: ${({theme}) => theme.sizing.spacing.sm}; + text-align: left; + color: ${({theme}) => theme.colors.text.primary}; + background: none; + border: none; + border-radius: ${({theme}) => theme.sizing.borderRadius.sm}; + + &:hover { + background: ${({theme}) => theme.colors.primary}; + color: ${({theme}) => theme.colors.background}; + } +`; + +export const ThemeMenu: React.FC = () => { + const [currentTheme, setTheme] = useTheme(); + const [isOpen, setIsOpen] = React.useState(false); + + const handleThemeChange = (themeName: keyof typeof themes) => { + setTheme(themeName); + setIsOpen(false); + }; + + return ( + + setIsOpen(!isOpen)}> + Theme: {currentTheme} + + {isOpen && ( + + {Object.keys(themes).map((themeName) => ( + handleThemeChange(themeName as keyof typeof themes)} + > + {themeName} + + ))} + + )} + + ); +}; diff --git a/webapp/chat-app/src/components/Menu/index.ts b/webapp/chat-app/src/components/Menu/index.ts new file mode 100644 index 00000000..1671a9ff --- /dev/null +++ b/webapp/chat-app/src/components/Menu/index.ts @@ -0,0 +1 @@ +export {ThemeMenu} from './ThemeMenu'; diff --git a/webapp/chat-app/src/components/MessageList.tsx b/webapp/chat-app/src/components/MessageList.tsx index 728f74e0..e2fa5fdf 100644 --- a/webapp/chat-app/src/components/MessageList.tsx +++ b/webapp/chat-app/src/components/MessageList.tsx @@ -1,7 +1,7 @@ import React from 'react'; import styled from 'styled-components'; -import { useSelector } from 'react-redux'; -import { RootState } from '../store'; +import {useSelector} from 'react-redux'; +import {RootState} from '../store'; const MessageListContainer = styled.div` flex: 1; @@ -36,7 +36,7 @@ const MessageList: React.FC = () => { return ( {messages.map((message) => ( - + {message.content} ))} diff --git a/webapp/chat-app/src/hooks/useTheme.ts b/webapp/chat-app/src/hooks/useTheme.ts index ef3d13ba..0c3a059d 100644 --- a/webapp/chat-app/src/hooks/useTheme.ts +++ b/webapp/chat-app/src/hooks/useTheme.ts @@ -1,19 +1,22 @@ -import React from 'react'; -import { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; - import { ThemeName } from '../types'; -import { setTheme } from '../store/slices/uiSlice'; -import { RootState } from '../store'; +import React, {useCallback} from 'react'; +import {useDispatch, useSelector} from 'react-redux'; +import {ThemeName} from '../types'; +import {setTheme} from '../store/slices/uiSlice'; +import {RootState} from '../store'; export const useTheme = (initialTheme?: ThemeName): [ThemeName, (theme: ThemeName) => void] => { const dispatch = useDispatch(); const currentTheme = useSelector((state: RootState) => state.ui.theme); - const updateTheme = useCallback((newTheme: ThemeName) => { - dispatch(setTheme(newTheme)); - localStorage.setItem('theme', newTheme); - }, [dispatch]); - // Use initialTheme if provided and no theme is set in state + const updateTheme = useCallback( + (newTheme: ThemeName) => { + dispatch(setTheme(newTheme)); + localStorage.setItem('theme', newTheme); + }, + [dispatch] + ); + + // Use initialTheme if provided and no theme is set in state React.useEffect(() => { if (initialTheme && !currentTheme) { updateTheme(initialTheme); @@ -21,4 +24,4 @@ export const useTheme = (initialTheme?: ThemeName): [ThemeName, (theme: ThemeNam }, [initialTheme, currentTheme, updateTheme]); return [currentTheme, updateTheme]; -}; \ No newline at end of file +}; diff --git a/webapp/chat-app/src/hooks/useWebSocket.ts b/webapp/chat-app/src/hooks/useWebSocket.ts index 17c64116..1d83f3da 100644 --- a/webapp/chat-app/src/hooks/useWebSocket.ts +++ b/webapp/chat-app/src/hooks/useWebSocket.ts @@ -2,12 +2,26 @@ import {useEffect, useState} from 'react'; import WebSocketService from '../services/websocket'; export const useWebSocket = (sessionId: string) => { + const [isConnected, setIsConnected] = useState(false); + useEffect(() => { + const handleConnection = () => { + setIsConnected(true); + }; + const handleDisconnection = () => { + setIsConnected(false); + }; + WebSocketService.addMessageHandler(handleConnection); WebSocketService.connect(sessionId); + return () => { + WebSocketService.removeMessageHandler(handleConnection); WebSocketService.disconnect(); }; }, [sessionId]); - return WebSocketService; + return { + send: (message: string) => WebSocketService.send(message), + isConnected + }; }; \ No newline at end of file diff --git a/webapp/chat-app/src/index.js b/webapp/chat-app/src/index.js index d563c0fb..87a4b309 100644 --- a/webapp/chat-app/src/index.js +++ b/webapp/chat-app/src/index.js @@ -4,14 +4,16 @@ import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render( - - - -); +if (typeof document !== 'undefined') { + const root = ReactDOM.createRoot(document.getElementById('root')); + root.render( + + + + ); +} // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); +reportWebVitals(); \ No newline at end of file diff --git a/webapp/chat-app/src/index.tsx b/webapp/chat-app/src/index.tsx index efdbfa6a..61ce8cd3 100644 --- a/webapp/chat-app/src/index.tsx +++ b/webapp/chat-app/src/index.tsx @@ -1,14 +1,16 @@ -import { Provider } from 'react-redux'; +import {Provider} from 'react-redux'; import React from 'react'; -import { createRoot } from 'react-dom/client'; +import {createRoot} from 'react-dom/client'; import App from './App'; -import { store } from './store'; +import {store} from './store'; import './index.css'; -const container = document.getElementById('root'); -const root = createRoot(container!); +const rootElement = document.getElementById('root'); +if (!rootElement) throw new Error('Failed to find the root element'); +const root = createRoot(rootElement); + root.render( -); +); \ No newline at end of file diff --git a/webapp/chat-app/src/services/websocket.ts b/webapp/chat-app/src/services/websocket.ts index 94287ff5..3a5c47b8 100644 --- a/webapp/chat-app/src/services/websocket.ts +++ b/webapp/chat-app/src/services/websocket.ts @@ -1,12 +1,14 @@ export class WebSocketService { private ws: WebSocket | null = null; private reconnectAttempts = 0; - private maxReconnectAttempts = 5; - private sessionId: string = ''; + private maxReconnectAttempts = 10; + private sessionId = ''; private messageHandlers: ((data: any) => void)[] = []; - private protocol: string = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + private protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; private host: string = window.location.hostname; private port: string = window.location.port; + private isReconnecting = false; + private connectionTimeout: NodeJS.Timeout | null = null; public getSessionId(): string { return this.sessionId; @@ -18,28 +20,65 @@ export class WebSocketService { connect(sessionId: string): void { try { + // Clear any existing connection timeout + if (this.connectionTimeout) { + clearTimeout(this.connectionTimeout); + } + this.sessionId = sessionId; const path = this.getWebSocketPath(); - this.ws = new WebSocket(`${this.protocol}//${this.host}:${this.port}${path}ws?sessionId=${sessionId}`); - this.setupEventHandlers(); + // Only create new connection if not already connected or reconnecting + if (!this.isConnected() && !this.isReconnecting) { + this.ws = new WebSocket( + `${this.protocol}//${this.host}:${this.port}${path}ws?sessionId=${sessionId}` + ); + this.setupEventHandlers(); + // Set connection timeout + this.connectionTimeout = setTimeout(() => { + if (this.ws?.readyState !== WebSocket.OPEN) { + this.ws?.close(); + this.attemptReconnect(); + } + }, 5000); + } } catch (error) { console.error('WebSocket connection error:', error); + this.attemptReconnect(); } } - private getWebSocketPath(): string { - const path = window.location.pathname; - const strings = path.split('/'); - return (strings.length >= 2 && strings[1] !== '' && strings[1] !== 'index.html') - ? '/' + strings[1] + '/' - : '/'; + + removeMessageHandler(handler: (data: any) => void): void { + this.messageHandlers = this.messageHandlers.filter((h) => h !== handler); } + addMessageHandler(handler: (data: any) => void): void { this.messageHandlers.push(handler); } - removeMessageHandler(handler: (data: any) => void): void { - this.messageHandlers = this.messageHandlers.filter(h => h !== handler); + + disconnect(): void { + if (this.ws) { + if (this.connectionTimeout) { + clearTimeout(this.connectionTimeout); + } + this.isReconnecting = false; + this.ws.close(); + this.ws = null; + } } + send(message: string): void { + if (this.ws?.readyState === WebSocket.OPEN) { + this.ws.send(message); + } + } + + private getWebSocketPath(): string { + const path = window.location.pathname; + const strings = path.split('/'); + return strings.length >= 2 && strings[1] !== '' && strings[1] !== 'index.html' + ? '/' + strings[1] + '/' + : '/'; + } private setupEventHandlers(): void { if (!this.ws) return; @@ -47,45 +86,43 @@ export class WebSocketService { this.ws.onopen = () => { console.log('WebSocket connected'); this.reconnectAttempts = 0; + this.isReconnecting = false; + if (this.connectionTimeout) { + clearTimeout(this.connectionTimeout); + } }; this.ws.onmessage = (event) => { - this.messageHandlers.forEach(handler => handler(event.data)); + this.messageHandlers.forEach((handler) => handler(event.data)); }; - this.ws.onclose = () => { console.log('WebSocket disconnected'); - this.attemptReconnect(); + if (!this.isReconnecting) { + this.attemptReconnect(); + } }; this.ws.onerror = (error) => { console.error('WebSocket error:', error); + if (this.ws?.readyState !== WebSocket.OPEN) { + this.attemptReconnect(); + } }; } private attemptReconnect(): void { + if (this.isReconnecting) return; + if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error('Max reconnection attempts reached'); return; } + this.isReconnecting = true; setTimeout(() => { this.reconnectAttempts++; this.connect(this.sessionId); - }, 1000 * Math.pow(2, this.reconnectAttempts)); - } - - send(message: string): void { - if (this.ws?.readyState === WebSocket.OPEN) { - this.ws.send(message); - } - } - - disconnect(): void { - if (this.ws) { - this.ws.close(); - this.ws = null; - } + }, Math.min(1000 * Math.pow(1.5, this.reconnectAttempts), 30000)); } } diff --git a/webapp/chat-app/src/store/slices/configSlice.ts b/webapp/chat-app/src/store/slices/configSlice.ts index 1c1ca9cc..fec5cbf8 100644 --- a/webapp/chat-app/src/store/slices/configSlice.ts +++ b/webapp/chat-app/src/store/slices/configSlice.ts @@ -1,5 +1,5 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { AppConfig } from '../../types'; +import {createSlice, PayloadAction} from '@reduxjs/toolkit'; +import {AppConfig} from '../../types'; const initialState: AppConfig = { singleInput: false, @@ -43,4 +43,4 @@ export const { setApplicationName, } = configSlice.actions; -export default configSlice.reducer; \ No newline at end of file +export default configSlice.reducer; diff --git a/webapp/chat-app/src/store/slices/messageSlice.ts b/webapp/chat-app/src/store/slices/messageSlice.ts index 2add554e..b64166bd 100644 --- a/webapp/chat-app/src/store/slices/messageSlice.ts +++ b/webapp/chat-app/src/store/slices/messageSlice.ts @@ -1,5 +1,5 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { Message } from '../../types'; +import {createSlice, PayloadAction} from '@reduxjs/toolkit'; +import {Message} from '../../types'; interface MessageState { messages: Message[]; @@ -68,4 +68,4 @@ export const { clearMessages, } = messageSlice.actions; -export default messageSlice.reducer; \ No newline at end of file +export default messageSlice.reducer; diff --git a/webapp/chat-app/src/store/slices/uiSlice.ts b/webapp/chat-app/src/store/slices/uiSlice.ts index f4da38af..4d0cc265 100644 --- a/webapp/chat-app/src/store/slices/uiSlice.ts +++ b/webapp/chat-app/src/store/slices/uiSlice.ts @@ -1,12 +1,18 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { ThemeName } from '../../themes/themes'; +import {createSlice, PayloadAction} from '@reduxjs/toolkit'; +import {ThemeName} from '../../themes/themes'; interface UiState { theme: ThemeName; + modalOpen: boolean; + modalType: string | null; + verboseMode: boolean; } const initialState: UiState = { - theme: 'main' + theme: 'main', + modalOpen: false, + modalType: null, + verboseMode: false }; const uiSlice = createSlice({ @@ -15,9 +21,20 @@ const uiSlice = createSlice({ reducers: { setTheme: (state, action: PayloadAction) => { state.theme = action.payload; + }, + showModal: (state, action: PayloadAction) => { + state.modalOpen = true; + state.modalType = action.payload; + }, + hideModal: (state) => { + state.modalOpen = false; + state.modalType = null; + }, + toggleVerbose: (state) => { + state.verboseMode = !state.verboseMode; } - } + }, }); -export const { setTheme } = uiSlice.actions; -export default uiSlice.reducer; +export const {setTheme, showModal, hideModal, toggleVerbose} = uiSlice.actions; +export default uiSlice.reducer; \ No newline at end of file diff --git a/webapp/chat-app/src/store/slices/userSlice.ts b/webapp/chat-app/src/store/slices/userSlice.ts index 276b44ac..cfc94857 100644 --- a/webapp/chat-app/src/store/slices/userSlice.ts +++ b/webapp/chat-app/src/store/slices/userSlice.ts @@ -1,5 +1,5 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { UserInfo } from '../../types'; +import {createSlice, PayloadAction} from '@reduxjs/toolkit'; +import {UserInfo} from '../../types'; const initialState: UserInfo = { name: '', @@ -31,4 +31,4 @@ const userSlice = createSlice({ export const { setUser, login, logout, updatePreferences } = userSlice.actions; -export default userSlice.reducer; \ No newline at end of file +export default userSlice.reducer; diff --git a/webapp/chat-app/src/themes/ThemeProvider.tsx b/webapp/chat-app/src/themes/ThemeProvider.tsx index 2b182ff9..f762b50b 100644 --- a/webapp/chat-app/src/themes/ThemeProvider.tsx +++ b/webapp/chat-app/src/themes/ThemeProvider.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { ThemeProvider as StyledThemeProvider } from 'styled-components'; -import { useSelector } from 'react-redux'; -import { RootState } from '../store'; -import { themes } from './themes'; +import {ThemeProvider as StyledThemeProvider} from 'styled-components'; +import {useSelector} from 'react-redux'; +import {RootState} from '../store'; +import {themes} from './themes'; interface ThemeProviderProps { children: React.ReactNode; @@ -14,4 +14,4 @@ export const ThemeProvider: React.FC = ({ children }) => { return {children}; }; -export default ThemeProvider; \ No newline at end of file +export default ThemeProvider; diff --git a/webapp/chat-app/src/themes/globalStyles.ts b/webapp/chat-app/src/themes/globalStyles.ts index 3ff79e92..10c7186a 100644 --- a/webapp/chat-app/src/themes/globalStyles.ts +++ b/webapp/chat-app/src/themes/globalStyles.ts @@ -1,4 +1,4 @@ -import { createGlobalStyle } from 'styled-components'; +import {createGlobalStyle} from 'styled-components'; export const GlobalStyle = createGlobalStyle` * { @@ -37,4 +37,4 @@ export const GlobalStyle = createGlobalStyle` input, textarea { font-family: inherit; } -`; \ No newline at end of file +`; diff --git a/webapp/chat-app/src/themes/index.ts b/webapp/chat-app/src/themes/index.ts index e62a0d24..6bbb6900 100644 --- a/webapp/chat-app/src/themes/index.ts +++ b/webapp/chat-app/src/themes/index.ts @@ -1,99 +1,9 @@ -import { DefaultTheme } from 'styled-components'; +import {themes} from './themes'; -export const mainTheme: DefaultTheme = { - colors: { - primary: '#007AFF', - secondary: '#5856D6', - background: '#FFFFFF', - surface: '#F2F2F7', - text: { - primary: '#000000', - secondary: '#3C3C43' - }, - border: '#E5E5E5', - error: '#FF3B30', - success: '#34C759', - warning: '#FF9500', - info: '#5856D6' - }, - sizing: { - spacing: { - xs: '0.25rem', - sm: '0.5rem', - md: '1rem', - lg: '1.5rem', - xl: '2rem' - }, - borderRadius: { - sm: '0.25rem', - md: '0.5rem', - lg: '1rem' - } - }, - typography: { - fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', - fontSize: { - xs: '0.75rem', - sm: '0.875rem', - md: '1rem', - lg: '1.25rem', - xl: '1.5rem' - }, - fontWeight: { - regular: 400, - medium: 500, - bold: 700 - } - }, - name: 'light' -}; -export const nightTheme: DefaultTheme = { - colors: { - primary: '#0A84FF', - secondary: '#5E5CE6', - background: '#000000', - surface: '#1C1C1E', - text: { - primary: '#FFFFFF', - secondary: '#EBEBF5' - }, - border: '#333333', - error: '#FF453A', - success: '#32D74B', - warning: '#FF9F0A', - info: '#5E5CE6' - }, - sizing: { - spacing: { - xs: '0.25rem', - sm: '0.5rem', - md: '1rem', - lg: '1.5rem', - xl: '2rem' - }, - borderRadius: { - sm: '0.25rem', - md: '0.5rem', - lg: '1rem' - } - }, - typography: { - fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', - fontSize: { - xs: '0.75rem', - sm: '0.875rem', - md: '1rem', - lg: '1.25rem', - xl: '1.5rem' - }, - fontWeight: { - regular: 400, - medium: 500, - bold: 700 - } - }, - name: 'dark' -}; +export {themes}; +export type {ThemeName} from './themes'; +export const mainTheme = themes.main; +export const nightTheme = themes.night; // Additional themes will be added here \ No newline at end of file diff --git a/webapp/chat-app/src/themes/themes.ts b/webapp/chat-app/src/themes/themes.ts index 638c0278..a669089c 100644 --- a/webapp/chat-app/src/themes/themes.ts +++ b/webapp/chat-app/src/themes/themes.ts @@ -1,8 +1,6 @@ -import { DefaultTheme } from 'styled-components'; // Define theme names export type ThemeName = 'main' | 'night' | 'forest' | 'pony' | 'alien'; - interface ThemeColors { primary: string; secondary: string; @@ -195,4 +193,4 @@ export const themes = { forest: forestTheme, pony: ponyTheme, alien: alienTheme, -}; \ No newline at end of file +};