diff --git a/src/App.tsx b/src/App.tsx index 02dff134..cdee8d40 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { ThemeProvider, createTheme } from "@mui/material/styles"; import { GlobalInfoProvider } from "./context/globalInfo"; import { PageWrapper } from "./pages/PageWrappper"; import i18n from "./i18n"; +import ThemeModeProvider from './context/theme-provider'; const theme = createTheme({ @@ -85,15 +86,23 @@ const theme = createTheme({ function App() { return ( - + + + + } /> + + + + + // - - - } /> - - + // + // + // } /> + // + // - + // ); } diff --git a/src/components/atoms/Loader.tsx b/src/components/atoms/Loader.tsx index 9d376bc7..0924b32f 100644 --- a/src/components/atoms/Loader.tsx +++ b/src/components/atoms/Loader.tsx @@ -1,11 +1,14 @@ import styled from "styled-components"; import { Stack } from "@mui/material"; +import { useThemeMode } from "../../context/theme-provider"; interface LoaderProps { text: string; } export const Loader: React.FC = ({ text }) => { + const { darkMode } = useThemeMode(); + return ( @@ -14,15 +17,19 @@ export const Loader: React.FC = ({ text }) => { - {text} + {text} ); }; -const StyledParagraph = styled.p` +interface StyledParagraphProps { + darkMode: boolean; +} + +const StyledParagraph = styled.p` font-size: large; font-family: inherit; - color: #333; + color: ${({ darkMode }) => (darkMode ? 'white' : '#333')}; margin-top: 20px; `; diff --git a/src/components/atoms/RecorderIcon.tsx b/src/components/atoms/RecorderIcon.tsx index afed1756..ee07ec72 100644 --- a/src/components/atoms/RecorderIcon.tsx +++ b/src/components/atoms/RecorderIcon.tsx @@ -14,7 +14,7 @@ export const RecordingIcon = () => { textIndent: 0, textTransform: 'none', }} - fill="white" + fill="black" d="m82.048,962.36c-0.18271-0.003-0.35147-0.001-0.53125,0.0312-0.69633,0.12662-1.3353,0.54943-1.7812,1.1562l-0.03125-0.0312-0.03125,0.0625-18.031,22.125h-44.438c-2.809,0-5.0938,2.2847-5.0938,5.0938v35.531c0,2.8091,2.2847,5.125,5.0938,5.125h20.562l-1.3125,4.5938h-0.71875c-1.1137,0-2.0312,0.9175-2.0312,2.0312v2.2188c0,1.1137,0.91751,2.0625,2.0312,2.0625h19.938c1.1137,0,2.0312-0.9488,2.0312-2.0625v-2.2188c0-1.1137-0.91751-2.0312-2.0312-2.0312h-0.71875l-1.3438-4.5938h20.438c2.809,0,5.0938-2.3159,5.0938-5.125v-35.531c0-1.7706-0.90663-3.3369-2.2812-4.25l10.531-17.625,0.03125-0.0625c0.84234-1.2783,0.51486-3.0308-0.75-3.9062l-3.0312-2.0938c-0.48208-0.33338-1.0456-0.49073-1.5938-0.5zm-0.21875,1.6875c0.28723-0.0523,0.57635,0.0338,0.84375,0.21875l3.0312,2.0938c0.53421,0.36973,0.65504,1.0569,0.28125,1.5938a0.85008,0.85008,0,0,0,-0.03125,0.0312l-17.906,30.062-9.0938-6.3125,22.094-27.125a0.85008,0.85008,0,0,0,0.03125,-0.0625c0.18694-0.26873,0.46277-0.4477,0.75-0.5zm-64.625,23.344,43.062,0-2.3438,2.9062-40.688,0c-0.0312-0.002-0.06255-0.002-0.09375,0-0.0312-0.002-0.06255-0.002-0.09375,0-0.38644,0.0753-0.69183,0.45007-0.6875,0.84375v34.844c0.003,0.4514,0.42377,0.857,0.875,0.8437h56.781c0.44088,0,0.84048-0.4028,0.84375-0.8437v-34.844c-0.008-0.25538-0.13841-0.50419-0.34375-0.65625l1.5-2.5c0.87419,0.61342,1.4375,1.6512,1.4375,2.8125v35.531c0,1.8967-1.5096,3.4063-3.4062,3.4063h-56.844c-1.8966,0-3.4062-1.5096-3.4062-3.4063v-35.531c0-1.8966,1.5096-3.4062,3.4062-3.4062zm0.875,4.5938,38.469,0-1.0312,1.25,0,0.0312c-0.48971,0.60518-0.64056,1.3922-0.5,2.0312,0.14234,0.64722,0.49536,1.1659,0.84375,1.6562a0.85008,0.85008,0,0,0,0.1875,0.21875l1.2812,0.875c-1.0387,0.79518-2.0706,1.1661-3.2188,1.6562-1.4337,0.61212-3.0045,1.4512-4.3438,3.375-1.1451,1.6448-1.0525,3.5446-0.78125,5.3437,0.27121,1.7991,0.70152,3.5802,0.5625,5.2188a0.85008,0.85008,0,0,0,1.2188,0.8437c1.4928-0.7039,3.3085-0.9361,5.0938-1.3125s3.6049-0.9489,4.75-2.5937c1.34-1.9249,1.5559-3.6628,1.625-5.2188,0.05552-1.2502,0.05447-2.363,0.4375-3.625l1.2812,0.875c1.2744,0.8814,3.0499,0.4785,3.8438-0.8437l0.03125-0.031,1.125-1.9063a0.85008,0.85008,0,0,0,0.03125,-0.0312l0.03125-0.0312a0.85008,0.85008,0,0,0,0.09375,-0.21875l4.0625-6.8125v32.406h-55.094v-33.156zm39.812,1.0625,9.3125,6.4375-0.84375,1.4062a0.85008,0.85008,0,0,0,-0.03125,0c-0.33037,0.5726-0.86691,0.7168-1.4062,0.3438l-2.1875-1.5-0.1875-0.15625-0.65625-0.4375-1.8438-1.2812-0.84375-0.59375-0.0625-0.0312-1.9688-1.3438c-0.25075-0.36937-0.4494-0.7387-0.5-0.96875-0.0558-0.25371-0.0497-0.34572,0.15625-0.59375l1.0625-1.2812zm0.84375,5.9688,0.34375,0.25,1.8438,1.25,0.375,0.25c-0.60662,1.6994-0.69236,3.2017-0.75,4.5-0.0657,1.481-0.18871,2.7295-1.3125,4.3438-0.76502,1.0988-2.0465,1.5537-3.7188,1.9062-1.3283,0.2801-2.854,0.5618-4.3438,1.0625-0.0521-1.5631-0.29881-3.0716-0.5-4.4062-0.25388-1.6841-0.29624-3.0262,0.46875-4.125,1.1246-1.6154,2.2602-2.1673,3.625-2.75,1.1932-0.5094,2.5901-1.1274,3.9688-2.2813zm-30.5,2.5313c-1.6815,0-3.0625,1.4119-3.0625,3.0937s1.381,3.0313,3.0625,3.0313,3.0625-1.3495,3.0625-3.0313-1.381-3.0937-3.0625-3.0937zm0,1.7187c0.76283,0,1.375,0.612,1.375,1.375s-0.61217,1.3438-1.375,1.3438-1.3438-0.5808-1.3438-1.3438,0.58092-1.375,1.3438-1.375zm8,5.6563c-3.3379,0.1812-7.1915,2.4749-10.344,4.6875-3.1522,2.2126-5.5625,4.4062-5.5625,4.4062-0.3273,0.3027-0.36527,0.8915-0.0625,1.2188,0.30273,0.3272,0.89151,0.334,1.2188,0.031,0,0,2.3185-2.1046,5.375-4.25s6.8989-4.2667,9.4688-4.4063c1.6177-0.088,4.3314,1.0381,6.5312,2.25,2.1999,1.212,3.9375,2.4375,3.9375,2.4375,0.35264,0.3353,1.001,0.2728,1.2812-0.125,0.28024-0.3977,0.12188-1.0307-0.3125-1.25,0,0-1.7602-1.2941-4.0625-2.5625-2.3024-1.2684-5.0831-2.567-7.4688-2.4375zm3.2812,22.562,12.344,0,1.3438,4.5312-15,0,1.3125-4.5312zm-3.7812,6.25,19.938,0c0.20135,0,0.3125,0.1424,0.3125,0.3437v2.2188c0,0.2013-0.11115,0.3437-0.3125,0.3437h-19.938c-0.20135,0-0.34375-0.1424-0.34375-0.3437v-2.2188c0-0.2013,0.1424-0.3437,0.34375-0.3437z" /> diff --git a/src/components/atoms/buttons/buttons.tsx b/src/components/atoms/buttons/buttons.tsx index afc4a483..0dd72e0c 100644 --- a/src/components/atoms/buttons/buttons.tsx +++ b/src/components/atoms/buttons/buttons.tsx @@ -1,26 +1,23 @@ import styled from 'styled-components'; +import { useThemeMode } from '../../../context/theme-provider'; -export const NavBarButton = styled.button<{ disabled: boolean }>` + + +export const NavBarButton = styled.button<{ disabled: boolean, mode: 'light' | 'dark' }>` margin-left: 10px; margin-right: 5px; padding: 0; border: none; - background-color: transparent; + background-color: ${mode => mode ? '#333' : '#ffffff'}; cursor: ${({ disabled }) => disabled ? 'default' : 'pointer'}; width: 24px; height: 24px; border-radius: 12px; outline: none; - color: ${({ disabled }) => disabled ? '#999' : '#333'}; + color: ${mode => mode ? '#ffffff' : '#333333'}; + + - ${({ disabled }) => disabled ? null : ` - &:hover { - background-color: #ddd; - } - &:active { - background-color: #d0d0d0; - } - `}; `; export const UrlFormButton = styled.button` diff --git a/src/components/molecules/ActionDescriptionBox.tsx b/src/components/molecules/ActionDescriptionBox.tsx index 45ec1641..97c979ec 100644 --- a/src/components/molecules/ActionDescriptionBox.tsx +++ b/src/components/molecules/ActionDescriptionBox.tsx @@ -5,19 +5,24 @@ import { useActionContext } from '../../context/browserActions'; import MaxunLogo from "../../assets/maxunlogo.png"; import { useTranslation } from 'react-i18next'; -const CustomBoxContainer = styled.div` +interface CustomBoxContainerProps { + isDarkMode: boolean; +} + +const CustomBoxContainer = styled.div` position: relative; min-width: 250px; width: auto; min-height: 100px; height: auto; - // border: 2px solid #ff00c3; border-radius: 5px; - background-color: white; + background-color: ${({ isDarkMode }) => (isDarkMode ? '#313438' : 'white')}; + color: ${({ isDarkMode }) => (isDarkMode ? 'white' : 'black')}; margin: 80px 13px 25px 13px; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); `; -const Triangle = styled.div` +const Triangle = styled.div` position: absolute; top: -15px; left: 50%; @@ -26,7 +31,7 @@ const Triangle = styled.div` height: 0; border-left: 20px solid transparent; border-right: 20px solid transparent; - border-bottom: 20px solid white; + border-bottom: 20px solid ${({ isDarkMode }) => (isDarkMode ? '#313438' : 'white')}; `; const Logo = styled.img` @@ -44,7 +49,8 @@ const Content = styled.div` text-align: left; `; -const ActionDescriptionBox = () => { + +const ActionDescriptionBox = ({ isDarkMode }: { isDarkMode: boolean }) => { const { t } = useTranslation(); const { getText, getScreenshot, getList, captureStage } = useActionContext() as { getText: boolean; @@ -93,9 +99,19 @@ const ActionDescriptionBox = () => { } - label={{text}} + label={ + + {text} + + } /> ))} @@ -112,9 +128,9 @@ const ActionDescriptionBox = () => { }; return ( - - - + + + {renderActionDescription()} diff --git a/src/components/molecules/ActionSettings.tsx b/src/components/molecules/ActionSettings.tsx index 79e120b8..a0a3aa59 100644 --- a/src/components/molecules/ActionSettings.tsx +++ b/src/components/molecules/ActionSettings.tsx @@ -1,16 +1,15 @@ import React, { useRef } from 'react'; import styled from "styled-components"; import { Button } from "@mui/material"; -//import { ActionDescription } from "../organisms/RightSidePanel"; import * as Settings from "./action-settings"; import { useSocketStore } from "../../context/socket"; interface ActionSettingsProps { action: string; + darkMode?: boolean; } -export const ActionSettings = ({ action }: ActionSettingsProps) => { - +export const ActionSettings = ({ action, darkMode = false }: ActionSettingsProps) => { const settingsRef = useRef<{ getSettings: () => object }>(null); const { socket } = useSocketStore(); @@ -20,30 +19,27 @@ export const ActionSettings = ({ action }: ActionSettingsProps) => { return ; case 'scroll': return ; - case 'scrape': - return ; + case 'scrape': + return ; case 'scrapeSchema': return ; default: return null; } - } + }; const handleSubmit = (event: React.SyntheticEvent) => { event.preventDefault(); - //get the data from settings const settings = settingsRef.current?.getSettings(); - //Send notification to the server and generate the pair socket?.emit(`action`, { action, settings }); - } + }; return (
- {/* Action settings: */} - +
setOpenModal(false)} modalStyle={modalStyle}> @@ -48,7 +60,14 @@ const BrowserRecordingSave = () => { - diff --git a/src/components/molecules/BrowserTabs.tsx b/src/components/molecules/BrowserTabs.tsx index 0ee05dfc..0648825a 100644 --- a/src/components/molecules/BrowserTabs.tsx +++ b/src/components/molecules/BrowserTabs.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { Box, IconButton, Tab, Tabs } from "@mui/material"; -import { AddButton } from "../atoms/buttons/AddButton"; import { useBrowserDimensionsStore } from "../../context/browserDimensions"; import { Close } from "@mui/icons-material"; +import { useThemeMode } from '../../context/theme-provider'; interface BrowserTabsProp { tabs: string[], @@ -28,15 +28,16 @@ export const BrowserTabs = ( handleChangeIndex(newValue); } }; + const isDarkMode = useThemeMode().darkMode; return ( - + {/* Synced border color */} { tabWasClosed = true; @@ -60,8 +65,7 @@ export const BrowserTabs = ( if (!tabWasClosed) { handleTabChange(index) } - } - } + }} label={tab} /> ); diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index c4b13e8c..d4bc8abe 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -17,6 +17,7 @@ import { apiUrl } from "../../apiConfig.js"; import Cookies from 'js-cookie'; import { useTranslation } from "react-i18next"; + interface IntegrationProps { isOpen: boolean; handleStart: (data: IntegrationSettings) => void; @@ -29,6 +30,20 @@ export interface IntegrationSettings { data: string; } +// Helper functions to replace js-cookie functionality +const getCookie = (name: string): string | null => { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) { + return parts.pop()?.split(';').shift() || null; + } + return null; +}; + +const removeCookie = (name: string): void => { + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`; +}; + export const IntegrationSettingsModal = ({ isOpen, handleStart, @@ -141,14 +156,14 @@ export const IntegrationSettingsModal = ({ useEffect(() => { // Check if there is a success message in cookies - const status = Cookies.get("robot_auth_status"); - const message = Cookies.get("robot_auth_message"); + const status = getCookie("robot_auth_status"); + const message = getCookie("robot_auth_message"); if (status === "success" && message) { notify("success", message); // Clear the cookies after reading - Cookies.remove("robot_auth_status"); - Cookies.remove("robot_auth_message"); + removeCookie("robot_auth_status"); + removeCookie("robot_auth_message"); } // Check if we're on the callback URL @@ -309,4 +324,4 @@ export const modalStyle = { height: "fit-content", display: "block", padding: "20px", -}; +}; \ No newline at end of file diff --git a/src/components/molecules/InterpretationLog.tsx b/src/components/molecules/InterpretationLog.tsx index 227e621c..8047d94b 100644 --- a/src/components/molecules/InterpretationLog.tsx +++ b/src/components/molecules/InterpretationLog.tsx @@ -17,6 +17,7 @@ import StorageIcon from '@mui/icons-material/Storage'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import { SidePanelHeader } from './SidePanelHeader'; import { useGlobalInfoStore } from '../../context/globalInfo'; +import { useThemeMode } from '../../context/theme-provider'; import { useTranslation } from 'react-i18next'; interface InterpretationLogProps { @@ -124,6 +125,8 @@ export const InterpretationLog: React.FC = ({ isOpen, se } }, [hasScrapeListAction, hasScrapeSchemaAction, hasScreenshotAction, setIsOpen]); + const { darkMode} = useThemeMode(); + return ( @@ -132,7 +135,7 @@ export const InterpretationLog: React.FC = ({ isOpen, se variant="contained" color="primary" sx={{ - marginTop: '10px', + marginTop: '10px', color: 'white', position: 'absolute', background: '#ff00c3', @@ -157,8 +160,8 @@ export const InterpretationLog: React.FC = ({ isOpen, se onOpen={toggleDrawer(true)} PaperProps={{ sx: { - background: 'white', - color: 'black', + background: `${darkMode ? '#1e2124' : 'white'}`, + color: `${darkMode ? 'white' : 'black'}`, padding: '10px', height: 500, width: width - 10, diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 2a38ad39..e089e558 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -4,17 +4,17 @@ import axios from 'axios'; import styled from "styled-components"; import { stopRecording } from "../../api/recording"; import { useGlobalInfoStore } from "../../context/globalInfo"; -import { IconButton, Menu, MenuItem, Typography, Chip, Button, Modal, Tabs, Tab, Box, Snackbar } from "@mui/material"; -import { AccountCircle, Logout, Clear, YouTube, X, Update, Close, Language } from "@mui/icons-material"; +import { IconButton, Menu, MenuItem, Typography, Chip, Button, Modal, Tabs, Tab, Box, Snackbar, Tooltip } from "@mui/material"; +import { AccountCircle, Logout, Clear, YouTube, X, Update, Close, Language, Brightness7, Brightness4 } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; import { SaveRecording } from '../molecules/SaveRecording'; import DiscordIcon from '../atoms/DiscordIcon'; import { apiUrl } from '../../apiConfig'; import MaxunLogo from "../../assets/maxunlogo.png"; +import { useThemeMode } from '../../context/theme-provider'; import packageJson from "../../../package.json" - interface NavBarProps { recordingName: string; isRecording: boolean; @@ -28,6 +28,7 @@ export const NavBar: React.FC = ({ const { state, dispatch } = useContext(AuthContext); const { user } = state; const navigate = useNavigate(); + const { darkMode, toggleTheme } = useThemeMode(); const { t, i18n } = useTranslation(); const [anchorEl, setAnchorEl] = useState(null); @@ -102,6 +103,22 @@ export const NavBar: React.FC = ({ localStorage.setItem("language", lang); }; + const renderThemeToggle = () => ( + + + {darkMode ? : } + + + ); + useEffect(() => { const checkForUpdates = async () => { const latestVersion = await fetchLatestVersion(); @@ -158,13 +175,13 @@ export const NavBar: React.FC = ({ }} /> )} - +
-
{t('navbar.project_name')}
+
{t('navbar.project_name')}
= ({ borderRadius: '5px', padding: '8px', marginRight: '10px', - '&:hover': { backgroundColor: 'white', color: '#ff00c3' } }}> {user.email} @@ -383,6 +399,7 @@ export const NavBar: React.FC = ({ + {renderThemeToggle()} ) : ( <> @@ -402,14 +419,15 @@ export const NavBar: React.FC = ({ )}
) : ( - <> + {t("Language")} @@ -467,23 +485,34 @@ export const NavBar: React.FC = ({ > Deutsch - - )} + + {renderThemeToggle()} + + )}
); }; -const NavBarWrapper = styled.div` +// Styled Components +const NavBarWrapper = styled.div<{ mode: 'light' | 'dark' }>` grid-area: navbar; - background-color: white; + background-color: ${({ mode }) => (mode === 'dark' ? '#1e2124' : '#ffffff')}; padding: 5px; display: flex; justify-content: space-between; - border-bottom: 1px solid #e0e0e0; + border-bottom: 1px solid ${({ mode }) => (mode === 'dark' ? '#333' : '#e0e0e0')}; `; -const ProjectName = styled.b` - color: #3f4853; +const ProjectName = styled.b<{ mode: 'light' | 'dark' }>` + color: ${({ mode }) => (mode === 'dark' ? '#ffffff' : '#3f4853')}; font-size: 1.3em; `; + +const NavBarRight = styled.div` + display: flex; + align-items: center; + justify-content: flex-end; + margin-left: auto; +`; + diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index ce3ee5ca..74a069d8 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -152,7 +152,16 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia -
diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index 6547d93b..ed637300 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -179,7 +179,11 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin color="primary" variant="outlined" style={{ marginLeft: '10px' }} - > + sx={{ + color: '#ff00c3 !important', + borderColor: '#ff00c3 !important', + backgroundColor: 'whitesmoke !important', + }}> {t('robot_edit.cancel')}
diff --git a/src/components/molecules/RunContent.tsx b/src/components/molecules/RunContent.tsx index dff44c6c..d335ada7 100644 --- a/src/components/molecules/RunContent.tsx +++ b/src/components/molecules/RunContent.tsx @@ -77,10 +77,50 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe - setTab(newTab)} aria-label="run-content-tabs"> - - - + setTab(newTab)} + aria-label="run-content-tabs" + sx={{ + // Remove the default blue indicator + '& .MuiTabs-indicator': { + backgroundColor: '#FF00C3', // Change to pink + }, + // Remove default transition effects + '& .MuiTab-root': { + '&.Mui-selected': { + color: '#FF00C3', + }, + } + }} + > + theme.palette.mode === 'dark' ? '#fff' : '#000', + '&:hover': { + color: '#FF00C3' + }, + '&.Mui-selected': { + color: '#FF00C3', + } + }} + /> + theme.palette.mode === 'dark' ? '#fff' : '#000', + '&:hover': { + color: '#FF00C3' + }, + '&.Mui-selected': { + color: '#FF00C3', + } + }} + /> +
                     {JSON.stringify(row.serializableOutput, null, 2)}
diff --git a/src/components/molecules/SaveRecording.tsx b/src/components/molecules/SaveRecording.tsx
index cc51f238..6a2d0d8b 100644
--- a/src/components/molecules/SaveRecording.tsx
+++ b/src/components/molecules/SaveRecording.tsx
@@ -77,7 +77,21 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
 
   return (
     
- diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 917696c9..f0a5816d 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -273,7 +273,16 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia - diff --git a/src/components/organisms/BrowserContent.tsx b/src/components/organisms/BrowserContent.tsx index 11af4f2f..a75698b0 100644 --- a/src/components/organisms/BrowserContent.tsx +++ b/src/components/organisms/BrowserContent.tsx @@ -152,8 +152,9 @@ export const BrowserContent = () => { // todo: use width from browser dimension once fixed browserWidth={900} handleUrlChanged={handleUrlChanged} + /> - +
); }; diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index b43ea75b..d9d21498 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -403,6 +403,11 @@ export const BrowserWindow = () => { overflow: 'hidden', padding: '5px 10px', }} + sx={{ + color: '#ff00c3 !important', + borderColor: '#ff00c3 !important', + backgroundColor: 'whitesmoke !important', + }} > void; } export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenuProps) => { + const theme = useTheme(); const {t} = useTranslation(); const handleChange = (event: React.SyntheticEvent, newValue: string) => { handleChangeContent(newValue); }; + // Define colors based on theme mode + const defaultcolor = theme.palette.mode === 'light' ? 'black' : 'white'; + + const buttonStyles = { + justifyContent: 'flex-start', + textAlign: 'left', + fontSize: 'medium', + padding: '6px 16px 6px 22px', + minHeight: '48px', + minWidth: '100%', + display: 'flex', + alignItems: 'center', + textTransform: 'none', + color: theme.palette.mode === 'light' ? '#6C6C6C' : 'inherit', + }; + + return ( - + ); -} - -const buttonStyles = { - justifyContent: 'flex-start', - textAlign: 'left', - fontSize: 'medium', - padding: '6px 16px 6px 22px', - minHeight: '48px', - minWidth: '100%', - display: 'flex', - alignItems: 'center', - textTransform: 'none', - color: '#6C6C6C !important', }; \ No newline at end of file diff --git a/src/components/organisms/ProxyForm.tsx b/src/components/organisms/ProxyForm.tsx index 46874349..234f13e3 100644 --- a/src/components/organisms/ProxyForm.tsx +++ b/src/components/organisms/ProxyForm.tsx @@ -1,8 +1,27 @@ import React, { useState, useEffect } from 'react'; import { styled } from '@mui/system'; -import { Alert, AlertTitle, TextField, Button, Switch, FormControlLabel, Box, Typography, Tabs, Tab, Table, TableContainer, TableHead, TableRow, TableBody, TableCell, Paper } from '@mui/material'; +import { + Alert, + AlertTitle, + TextField, + Button, + Switch, + FormControlLabel, + Box, + Typography, + Tabs, + Tab, + Table, + TableContainer, + TableHead, + TableRow, + TableBody, + TableCell, + Paper +} from '@mui/material'; import { sendProxyConfig, getProxyConfig, testProxyConfig, deleteProxyConfig } from '../../api/proxy'; import { useGlobalInfoStore } from '../../context/globalInfo'; +import { useThemeMode } from '../../context/theme-provider'; import { useTranslation } from 'react-i18next'; const FormContainer = styled(Box)({ @@ -134,16 +153,20 @@ const ProxyForm: React.FC = () => { fetchProxyConfig(); }, []); + const theme = useThemeMode(); + const isDarkMode = theme.darkMode; + return ( <> {t('proxy.title')} - + - + + {tabIndex === 0 && ( isProxyConfigured ? ( @@ -236,13 +259,19 @@ const ProxyForm: React.FC = () => { {t('proxy.coming_soon')} + + {/* )} + {t('proxy.alert.title')}
@@ -257,6 +286,7 @@ const ProxyForm: React.FC = () => {
{t('proxy.alert.wrong_way')}
+ {t('proxy.alert.proxy_url')} http://myusername:mypassword@proxy.com:1337
diff --git a/src/components/organisms/RightSidePanel.tsx b/src/components/organisms/RightSidePanel.tsx index d4670d4f..908aa600 100644 --- a/src/components/organisms/RightSidePanel.tsx +++ b/src/components/organisms/RightSidePanel.tsx @@ -22,6 +22,7 @@ import { emptyWorkflow } from "../../shared/constants"; import { getActiveWorkflow } from "../../api/workflow"; import DeleteIcon from '@mui/icons-material/Delete'; import ActionDescriptionBox from '../molecules/ActionDescriptionBox'; +import { useThemeMode } from '../../context/theme-provider'; import { useTranslation } from 'react-i18next'; const fetchWorkflow = (id: string, callback: (response: WorkflowFile) => void) => { @@ -461,15 +462,18 @@ export const RightSidePanel: React.FC = ({ onFinishCapture // Disable the button if there are no valid list selectors or if there are unconfirmed list text fields return !hasValidListSelector || hasUnconfirmedListTextFields; }, [captureStage, browserSteps, hasUnconfirmedListTextFields]); - + + const theme = useThemeMode(); + const isDarkMode = theme.darkMode; return ( {/* Last action: {` ${lastAction}`} */} - - - {!getText && !getScreenshot && !getList && showCaptureList && } + + + {!getText && !getScreenshot && !getList && showCaptureList && } + {getList && ( <> @@ -477,6 +481,11 @@ export const RightSidePanel: React.FC = ({ onFinishCapture @@ -485,13 +494,26 @@ export const RightSidePanel: React.FC = ({ onFinishCapture variant="outlined" onClick={handleConfirmListCapture} disabled={captureStage === 'initial' ? isConfirmCaptureDisabled : hasUnconfirmedListTextFields} + sx={{ + color: '#ff00c3 !important', + borderColor: '#ff00c3 !important', + backgroundColor: 'whitesmoke !important', + }} > {captureStage === 'initial' ? t('right_panel.buttons.confirm_capture') : captureStage === 'pagination' ? t('right_panel.buttons.confirm_pagination') : captureStage === 'limit' ? t('right_panel.buttons.confirm_limit') : t('right_panel.buttons.finish_capture')} - @@ -500,11 +522,55 @@ export const RightSidePanel: React.FC = ({ onFinishCapture {showPaginationOptions && ( {t('right_panel.pagination.title')} - - - - - + + + + + )} {showLimitOptions && ( @@ -550,9 +616,11 @@ export const RightSidePanel: React.FC = ({ onFinishCapture marginLeft: '10px', '& input': { padding: '10px', - background: 'white', + }, - width: '150px', // Ensure the text field does not go outside the panel + width: '150px', + background: isDarkMode ? "#1E2124" : 'white', + color: isDarkMode ? "white" : 'black', // Ensure the text field does not go outside the panel }} /> )} @@ -560,27 +628,59 @@ export const RightSidePanel: React.FC = ({ onFinishCapture )} + {/* {!getText && !getScreenshot && !getList && showCaptureText && } */} + {!getText && !getScreenshot && !getList && showCaptureText && } {getText && <> - - + + } + {/* {!getText && !getScreenshot && !getList && showCaptureScreenshot && } */} {!getText && !getScreenshot && !getList && showCaptureScreenshot && } {getScreenshot && ( - + )} {browserSteps.map(step => ( - handleMouseEnter(step.id)} onMouseLeave={() => handleMouseLeave(step.id)} sx={{ padding: '10px', margin: '11px', borderRadius: '5px', position: 'relative', background: 'white' }}> + handleMouseEnter(step.id)} onMouseLeave={() => handleMouseLeave(step.id)} sx={{ padding: '10px', margin: '11px', borderRadius: '5px', position: 'relative', background: isDarkMode ? "#1E2124" : 'white', color: isDarkMode ? "white" : 'black' }}> { step.type === 'text' && ( <> @@ -601,6 +701,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture ) }} + sx={{ background: isDarkMode ? "#1E2124" : 'white', color: isDarkMode ? "white" : 'black' }} /> = ({ onFinishCapture ) }} + /> {!confirmedTextSteps[step.id] ? ( @@ -648,7 +750,7 @@ export const RightSidePanel: React.FC = ({ onFinishCapture <> {t('right_panel.messages.list_selected')} {Object.entries(step.fields).map(([key, field]) => ( - + = ({ onFinishCapture ) - }} + }} /> = ({ onFinishCapture ) }} + /> {!confirmedListTextFields[step.id]?.[key] ? ( diff --git a/src/context/theme-provider.tsx b/src/context/theme-provider.tsx new file mode 100644 index 00000000..d53ced24 --- /dev/null +++ b/src/context/theme-provider.tsx @@ -0,0 +1,256 @@ +import React, { createContext, useContext, useState, useEffect } from 'react'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import CssBaseline from '@mui/material/CssBaseline'; + +const lightTheme = createTheme({ + palette: { + primary: { + main: "#ff00c3", + contrastText: "#ffffff", + }, + }, + components: { + MuiButton: { + styleOverrides: { + root: { + // Default styles for all buttons (optional) + textTransform: "none", + }, + containedPrimary: { + // Styles for 'contained' variant with 'primary' color + "&:hover": { + backgroundColor: "#ff66d9", + }, + }, + outlined: { + // Apply white background for all 'outlined' variant buttons + backgroundColor: "#ffffff", + "&:hover": { + backgroundColor: "#f0f0f0", // Optional lighter background on hover + }, + }, + }, + }, + MuiLink: { + styleOverrides: { + root: { + "&:hover": { + color: "#ff00c3", + }, + }, + }, + }, + MuiIconButton: { + styleOverrides: { + root: { + // '&:hover': { + // color: "#ff66d9", + // }, + }, + }, + }, + MuiTab: { + styleOverrides: { + root: { + textTransform: "none", + }, + }, + }, + MuiAlert: { + styleOverrides: { + standardInfo: { + backgroundColor: "#fce1f4", + color: "#ff00c3", + "& .MuiAlert-icon": { + color: "#ff00c3", + }, + }, + }, + }, + MuiAlertTitle: { + styleOverrides: { + root: { + "& .MuiAlert-icon": { + color: "#ffffff", + }, + }, + }, + }, + }, +}); + +const darkTheme = createTheme({ + palette: { + mode: 'dark', + primary: { + main: "#ff00c3", + contrastText: "#ffffff", + }, + background: { + default: '#121212', + paper: '#1e1e1e', + }, + text: { + primary: '#ffffff', + secondary: '#b3b3b3', + }, + }, + components: { + MuiButton: { + styleOverrides: { + root: { + textTransform: "none", + color: '#ffffff', + '&.MuiButton-outlined': { + borderColor: '#ffffff', + color: '#ffffff', + "&:hover": { + borderColor: '#ffffff', + backgroundColor: 'rgba(255, 255, 255, 0.08)', + }, + }, + }, + containedPrimary: { + "&:hover": { + backgroundColor: "#ff66d9", + }, + }, + outlined: { + // Dark mode outlined buttons + backgroundColor: '#1e1e1e', + borderColor: '#ff00c3', + color: '#ff00c3', + "&:hover": { + backgroundColor: 'rgba(255, 0, 195, 0.08)', + borderColor: '#ff66d9', + }, + }, + }, + }, + MuiLink: { + styleOverrides: { + root: { + color: '#ff66d9', + "&:hover": { + color: "#ff00c3", + }, + }, + }, + }, + MuiIconButton: { + styleOverrides: { + root: { + color: '#ffffff', + "&:hover": { + backgroundColor: 'rgba(255, 0, 195, 0.08)', + }, + }, + }, + }, + MuiTab: { + styleOverrides: { + root: { + textTransform: "none", + color: '#ffffff', + "&.Mui-selected": { + color: '#ff00c3', + }, + }, + }, + }, + MuiAlert: { + styleOverrides: { + standardInfo: { + backgroundColor: "rgba(255, 0, 195, 0.15)", + color: "#ff66d9", + "& .MuiAlert-icon": { + color: "#ff66d9", + }, + }, + }, + }, + MuiAlertTitle: { + styleOverrides: { + root: { + "& .MuiAlert-icon": { + color: "#ff66d9", + }, + }, + }, + }, + // Additional dark mode specific components + MuiPaper: { + styleOverrides: { + root: { + backgroundColor: '#1e1e1e', + }, + }, + }, + MuiAppBar: { + styleOverrides: { + root: { + backgroundColor: '#121212', + }, + }, + }, + MuiDrawer: { + styleOverrides: { + paper: { + backgroundColor: '#121212', + }, + }, + }, + MuiTableCell: { + styleOverrides: { + root: { + borderBottom: '1px solid rgba(255, 255, 255, 0.12)', + }, + }, + }, + MuiDivider: { + styleOverrides: { + root: { + borderColor: 'rgba(255, 255, 255, 0.12)', + }, + }, + }, + }, +}); + +const ThemeModeContext = createContext({ + toggleTheme: () => {}, + darkMode: false, +}); + +export const useThemeMode = () => useContext(ThemeModeContext); + +const ThemeModeProvider = ({ children }: { children: React.ReactNode }) => { + // Load saved mode from localStorage or default to light mode + const [darkMode, setDarkMode] = useState(() => { + const savedMode = localStorage.getItem('darkMode'); + return savedMode ? JSON.parse(savedMode) : false; + }); + + const toggleTheme = () => { + setDarkMode((prevMode: any) => { + const newMode = !prevMode; + localStorage.setItem('darkMode', JSON.stringify(newMode)); // Save new mode to localStorage + return newMode; + }); + }; + + useEffect(() => { + localStorage.setItem('darkMode', JSON.stringify(darkMode)); // Save initial mode + }, [darkMode]); + + return ( + + + + {children} + + + ); +}; + +export default ThemeModeProvider; diff --git a/src/index.css b/src/index.css index 721e4d67..252b737a 100644 --- a/src/index.css +++ b/src/index.css @@ -11,6 +11,7 @@ body { padding: 0; scrollbar-gutter: stable; overflow-y: auto; + } html { @@ -43,6 +44,7 @@ code { align-items: center; overflow: hidden; position: relative; + } #browser-content { @@ -54,6 +56,11 @@ code { transform-origin: top left; /* Keep the position fixed */ } + +#browser { + +} + #browser-window { overflow-y: auto; height: 100%; diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index ba0f377e..1b34a1db 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,12 +1,13 @@ import axios from "axios"; -import { useState, useContext, useEffect, FormEvent } from "react"; +import { useState, useContext, useEffect } from "react"; import { useNavigate, Link } from "react-router-dom"; import { AuthContext } from "../context/auth"; -import { Box, Typography, TextField, Button, CircularProgress, Grid } from "@mui/material"; +import { Box, Typography, TextField, Button, CircularProgress } from "@mui/material"; import { useGlobalInfoStore } from "../context/globalInfo"; import { apiUrl } from "../apiConfig"; import { useTranslation } from 'react-i18next'; import i18n from '../i18n'; +import { useThemeMode } from "../context/theme-provider"; const Login = () => { const { t } = useTranslation(); @@ -17,12 +18,14 @@ const Login = () => { email: "", password: "", }); + const [loading, setLoading] = useState(false); const { notify } = useGlobalInfoStore(); const { email, password } = form; const { state, dispatch } = useContext(AuthContext); const { user } = state; + const { darkMode } = useThemeMode(); const navigate = useNavigate(); @@ -41,10 +44,11 @@ const Login = () => { e.preventDefault(); setLoading(true); try { - const { data } = await axios.post(`${apiUrl}/auth/login`, { - email, - password, - }); + const { data } = await axios.post( + `${apiUrl}/auth/login`, + { email, password }, + { withCredentials: true } + ); dispatch({ type: "LOGIN", payload: data }); notify("success", t('login.welcome_notification')); window.localStorage.setItem("user", JSON.stringify(data)); @@ -64,6 +68,7 @@ const Login = () => { maxHeight: "100vh", mt: 6, padding: 4, + backgroundColor: darkMode ? "#121212" : "#ffffff", }} > { onSubmit={submitForm} sx={{ textAlign: "center", - backgroundColor: "#ffffff", + backgroundColor: darkMode ? "#1e1e1e" : "#ffffff", + color: darkMode ? "#ffffff" : "#333333", padding: 6, borderRadius: 5, boxShadow: "0px 20px 40px rgba(0, 0, 0, 0.2), 0px -5px 10px rgba(0, 0, 0, 0.15)", display: "flex", flexDirection: "column", alignItems: "center", - maxWidth: 400, + maxWidth: 500, width: "100%", }} > @@ -112,7 +118,10 @@ const Login = () => { fullWidth variant="contained" color="primary" - sx={{ mt: 2, mb: 2 }} + sx={{ + mt: 2, + mb: 2, + }} disabled={loading || !email || !password} > {loading ? ( diff --git a/src/pages/PageWrappper.tsx b/src/pages/PageWrappper.tsx index 87157349..5a00cc4a 100644 --- a/src/pages/PageWrappper.tsx +++ b/src/pages/PageWrappper.tsx @@ -12,6 +12,7 @@ import Login from './Login'; import Register from './Register'; import UserRoute from '../routes/userRoute'; import { Routes, Route, useNavigate } from 'react-router-dom'; +import { AppBar } from '@mui/material'; export const PageWrapper = () => { const [open, setOpen] = useState(false); @@ -50,7 +51,9 @@ export const PageWrapper = () => { + {!browserId && } + }> } /> diff --git a/src/pages/RecordingPage.tsx b/src/pages/RecordingPage.tsx index 82fa7e32..2b5544de 100644 --- a/src/pages/RecordingPage.tsx +++ b/src/pages/RecordingPage.tsx @@ -15,6 +15,7 @@ import { editRecordingFromStorage } from "../api/storage"; import { WhereWhatPair } from "maxun-core"; import styled from "styled-components"; import BrowserRecordingSave from '../components/molecules/BrowserRecordingSave'; +import { useThemeMode } from '../context/theme-provider'; import { useTranslation } from 'react-i18next'; interface RecordingPageProps { @@ -27,6 +28,7 @@ export interface PairForEdit { } export const RecordingPage = ({ recordingName }: RecordingPageProps) => { + const { darkMode } = useThemeMode(); const { t } = useTranslation(); const [isLoaded, setIsLoaded] = React.useState(false); const [hasScrollbar, setHasScrollbar] = React.useState(false); @@ -34,6 +36,7 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => { pair: null, index: 0, }); + const [showOutputData, setShowOutputData] = useState(false); const browserContentRef = React.useRef(null); @@ -57,15 +60,20 @@ export const RecordingPage = ({ recordingName }: RecordingPageProps) => { useEffect(() => changeBrowserDimensions(), [isLoaded]) useEffect(() => { - document.body.style.background = 'radial-gradient(circle, rgba(255, 255, 255, 1) 0%, rgba(232, 191, 222, 1) 100%, rgba(255, 255, 255, 1) 100%)'; - document.body.style.filter = 'progid:DXImageTransform.Microsoft.gradient(startColorstr="#ffffff",endColorstr="#ffffff",GradientType=1);' + if (darkMode) { + + document.body.style.background = 'rgba(18,18,18,1)'; + + } else { + document.body.style.background = 'radial-gradient(circle, rgba(255, 255, 255, 1) 0%, rgba(232, 191, 222, 1) 100%, rgba(255, 255, 255, 1) 100%)'; + document.body.style.filter = 'progid:DXImageTransform.Microsoft.gradient(startColorstr="#ffffff",endColorstr="#ffffff",GradientType=1);' + } return () => { - // Cleanup the background when leaving the page document.body.style.background = ''; document.body.style.filter = ''; }; - }, []); + }, [darkMode]); useEffect(() => { let isCancelled = false; diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index b1d2428f..d9c698a2 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -5,11 +5,11 @@ import { AuthContext } from "../context/auth"; import { Box, Typography, TextField, Button, CircularProgress } from "@mui/material"; import { useGlobalInfoStore } from "../context/globalInfo"; import { apiUrl } from "../apiConfig"; +import { useThemeMode } from "../context/theme-provider"; import { useTranslation } from 'react-i18next'; import i18n from '../i18n'; - const Register = () => { const {t} = useTranslation(); const [form, setForm] = useState({ @@ -22,6 +22,7 @@ const Register = () => { const { state, dispatch } = useContext(AuthContext); const { user } = state; + const { darkMode } = useThemeMode(); const navigate = useNavigate(); @@ -40,18 +41,14 @@ const Register = () => { e.preventDefault(); setLoading(true); try { - const { data } = await axios.post(`${apiUrl}/auth/register`, { - email, - password, - }); + const { data } = await axios.post(`${apiUrl}/auth/register`, { email, password }); + console.log(data); dispatch({ type: "LOGIN", payload: data }); notify("success", t('register.welcome_notification')); window.localStorage.setItem("user", JSON.stringify(data)); navigate("/"); } catch (error:any) { - notify("error", error.response.data || t('register.error_notification')); - setLoading(false); } }; @@ -65,25 +62,38 @@ const Register = () => { maxHeight: "100vh", mt: 6, padding: 4, + backgroundColor: darkMode ? "#121212" : "#ffffff", + }} > - logo + logo {t('register.title')} @@ -113,7 +123,10 @@ const Register = () => { fullWidth variant="contained" color="primary" - sx={{ mt: 2, mb: 2 }} + sx={{ + mt: 2, + mb: 2, + }} disabled={loading || !email || !password} > {loading ? ( @@ -125,10 +138,9 @@ const Register = () => { t('register.button') )} - + {t('register.register_prompt')}{" "} - - + {t('register.login_link')}