feat: move robot create modal to page

This commit is contained in:
Rohit Rajan
2025-09-29 16:14:16 +05:30
parent d2e0324312
commit d98531b838
3 changed files with 353 additions and 14 deletions

View File

@@ -278,20 +278,8 @@ export const RecordingsTable = ({
}, [setRecordings, notify, t]);
const handleNewRecording = useCallback(async () => {
const canCreateRecording = await canCreateBrowserInState("recording");
if (!canCreateRecording) {
const activeBrowserId = await getActiveBrowserId();
if (activeBrowserId) {
setActiveBrowserId(activeBrowserId);
setWarningModalOpen(true);
} else {
notify('warning', t('recordingtable.notifications.browser_limit_warning'));
}
} else {
setModalOpen(true);
}
}, []);
navigate('/robots/create');
}, [navigate]);
const notifyRecordingTabsToClose = (browserId: string) => {
const closeMessage = {

View File

@@ -0,0 +1,349 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import {
Box,
Typography,
TextField,
Button,
FormControlLabel,
Checkbox,
IconButton,
Grid,
Card,
CircularProgress,
Container,
CardContent
} from '@mui/material';
import { ArrowBack, PlayCircleOutline, Article } from '@mui/icons-material';
import { useGlobalInfoStore } from '../../../context/globalInfo';
import { canCreateBrowserInState, getActiveBrowserId, stopRecording } from '../../../api/recording';
import { AuthContext } from '../../../context/auth';
import { GenericModal } from '../../ui/GenericModal';
const RobotCreate: React.FC = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const { setBrowserId, setRecordingUrl, notify, setRecordingId } = useGlobalInfoStore();
const [url, setUrl] = useState('');
const [needsLogin, setNeedsLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isWarningModalOpen, setWarningModalOpen] = useState(false);
const [activeBrowserId, setActiveBrowserId] = useState('');
const { state } = React.useContext(AuthContext);
const { user } = state;
const handleStartRecording = async () => {
if (!url.trim()) {
notify('error', 'Please enter a valid URL');
return;
}
setIsLoading(true);
try {
const canCreateRecording = await canCreateBrowserInState("recording");
if (!canCreateRecording) {
const activeBrowser = await getActiveBrowserId();
if (activeBrowser) {
setActiveBrowserId(activeBrowser);
setWarningModalOpen(true);
} else {
notify('warning', t('recordingtable.notifications.browser_limit_warning'));
}
setIsLoading(false);
return;
}
setBrowserId('new-recording');
setRecordingUrl(url);
window.sessionStorage.setItem('browserId', 'new-recording');
window.sessionStorage.setItem('recordingUrl', url);
window.sessionStorage.setItem('initialUrl', url);
window.sessionStorage.setItem('needsLogin', needsLogin.toString());
const sessionId = Date.now().toString();
window.sessionStorage.setItem('recordingSessionId', sessionId);
window.open(`/recording-setup?session=${sessionId}`, '_blank');
window.sessionStorage.setItem('nextTabIsRecording', 'true');
// Reset loading state immediately after opening new tab
setIsLoading(false);
navigate('/robots');
} catch (error) {
console.error('Error starting recording:', error);
notify('error', 'Failed to start recording. Please try again.');
setIsLoading(false);
}
};
const handleDiscardAndCreate = async () => {
if (activeBrowserId) {
await stopRecording(activeBrowserId);
notify('warning', t('browser_recording.notifications.terminated'));
}
setWarningModalOpen(false);
setIsLoading(false);
// Continue with the original Recording logic
setBrowserId('new-recording');
setRecordingUrl(url);
window.sessionStorage.setItem('browserId', 'new-recording');
window.sessionStorage.setItem('recordingUrl', url);
window.sessionStorage.setItem('initialUrl', url);
window.sessionStorage.setItem('needsLogin', needsLogin.toString());
const sessionId = Date.now().toString();
window.sessionStorage.setItem('recordingSessionId', sessionId);
window.open(`/recording-setup?session=${sessionId}`, '_blank');
window.sessionStorage.setItem('nextTabIsRecording', 'true');
navigate('/robots');
};
return (
<Container maxWidth="md" sx={{ py: 4 }}>
<Box>
<Box display="flex" alignItems="center" mb={3}>
<IconButton
onClick={() => navigate('/robots')}
sx={{
ml: -1,
mr: 1,
color: theme => theme.palette.text.primary,
backgroundColor: 'transparent !important',
'&:hover': {
backgroundColor: 'transparent !important',
},
'&:active': {
backgroundColor: 'transparent !important',
},
'&:focus': {
backgroundColor: 'transparent !important',
},
'&:focus-visible': {
backgroundColor: 'transparent !important',
},
}}
disableRipple
aria-label="Go back"
>
<ArrowBack />
</IconButton>
<Typography variant="h5" component="h1">
New Data Extraction Robot
</Typography>
</Box>
<Card sx={{ mb: 4, p: 4, textAlign: 'center' }}>
<Box display="flex" flexDirection="column" alignItems="center">
{/* Logo (kept as original) */}
<img
src="https://ik.imagekit.io/ys1blv5kv/maxunlogo.png"
width={73}
height={65}
style={{
borderRadius: '5px',
marginBottom: '30px'
}}
alt="Maxun Logo"
/>
{/* Origin URL Input */}
<Box sx={{ width: '100%', maxWidth: 700, mb: 2 }}>
<TextField
placeholder="Example: https://www.ycombinator.com/companies/"
variant="outlined"
fullWidth
value={url}
onChange={(e) => setUrl(e.target.value)}
/>
</Box>
{/* Checkbox */}
<Box sx={{ width: '100%', maxWidth: 700, mb: 3, textAlign: 'left' }}>
<FormControlLabel
control={
<Checkbox
checked={needsLogin}
onChange={(e) => setNeedsLogin(e.target.checked)}
color="primary"
/>
}
label="This website needs logging in."
/>
</Box>
{/* Button */}
<Button
variant="contained"
fullWidth
onClick={handleStartRecording}
disabled={!url.trim() || isLoading}
sx={{
bgcolor: '#ff00c3',
py: 1.4,
fontSize: '1rem',
textTransform: 'none',
maxWidth: 700,
borderRadius: 2
}}
startIcon={isLoading ? <CircularProgress size={20} color="inherit" /> : null}
>
{isLoading ? 'Starting...' : 'Start Recording'}
</Button>
</Box>
</Card>
<Box mt={6} textAlign="center">
<Typography variant="h6" gutterBottom>
First time creating a robot?
</Typography>
<Typography variant="body2" color="text.secondary" mb={3}>
Get help and learn how to use Maxun effectively.
</Typography>
<Grid container spacing={3} justifyContent="center">
{/* YouTube Tutorials */}
<Grid item xs={12} sm={6} md={4}>
<Card
sx={{
height: 140,
cursor: "pointer",
}}
onClick={() => window.open("https://www.youtube.com/@MaxunOSS/videos", "_blank")}
>
<CardContent
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center", // center content
height: "100%",
textAlign: "center",
p: 2,
color: (theme) =>
theme.palette.mode === 'light' ? 'rgba(0, 0, 0, 0.54)' : '',
}}
>
<PlayCircleOutline sx={{ fontSize: "32px", mb: 2 }} />
<Box sx={{ textAlign: "center" }}>
<Typography variant="body1" fontWeight="600" sx={{ lineHeight: 1.2 }}>
Video Tutorials
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ lineHeight: 1.4, mt: 1 }}>
Watch step-by-step guides
</Typography>
</Box>
</CardContent>
</Card>
</Grid>
{/* Documentation */}
<Grid item xs={12} sm={6} md={4}>
<Card
sx={{
height: 140,
cursor: "pointer",
}}
onClick={() => window.open("https://docs.maxun.dev", "_blank")}
>
<CardContent
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center", // center everything
height: "100%",
textAlign: "center",
p: 2,
color: (theme) =>
theme.palette.mode === 'light' ? 'rgba(0, 0, 0, 0.54)' : '',
}}
>
<Article sx={{ fontSize: "32px", mb: 2 }} />
<Box sx={{ textAlign: "center" }}>
<Typography variant="body1" fontWeight="600" sx={{ lineHeight: 1.2 }}>
Documentation
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ lineHeight: 1.4, mt: 1 }}>
Explore detailed guides
</Typography>
</Box>
</CardContent>
</Card>
</Grid>
</Grid>
</Box>
</Box>
<GenericModal isOpen={isWarningModalOpen} onClose={() => {
setWarningModalOpen(false);
setIsLoading(false);
}} modalStyle={modalStyle}>
<div style={{ padding: '10px' }}>
<Typography variant="h6" gutterBottom>{t('recordingtable.warning_modal.title')}</Typography>
<Typography variant="body1" style={{ marginBottom: '20px' }}>
{t('recordingtable.warning_modal.message')}
</Typography>
<Box display="flex" justifyContent="space-between" mt={2}>
<Button
onClick={handleDiscardAndCreate}
variant="contained"
color="error"
>
{t('recordingtable.warning_modal.discard_and_create')}
</Button>
<Button
onClick={() => {
setWarningModalOpen(false);
setIsLoading(false);
}}
variant="outlined"
>
{t('recordingtable.warning_modal.cancel')}
</Button>
</Box>
</div>
</GenericModal>
</Container>
);
};
export default RobotCreate;
const modalStyle = {
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '30%',
backgroundColor: 'background.paper',
p: 4,
height: 'fit-content',
display: 'block',
padding: '20px',
};

View File

@@ -12,6 +12,7 @@ import Register from './Register';
import UserRoute from '../routes/userRoute';
import { Routes, Route, useNavigate, Navigate } from 'react-router-dom';
import { NotFoundPage } from '../components/dashboard/NotFound';
import RobotCreate from '../components/robot/pages/RobotCreate';
export const PageWrapper = () => {
const [open, setOpen] = useState(false);
@@ -94,6 +95,7 @@ export const PageWrapper = () => {
<Routes>
<Route element={<UserRoute />}>
<Route path="/" element={<Navigate to="/robots" replace />} />
<Route path="/robots/create" element={<RobotCreate />} />
<Route path="/robots/*" element={<MainPage handleEditRecording={handleEditRecording} initialContent="robots" />} />
<Route path="/runs/*" element={<MainPage handleEditRecording={handleEditRecording} initialContent="runs" />} />
<Route path="/proxy" element={<MainPage handleEditRecording={handleEditRecording} initialContent="proxy" />} />