Merge pull request #958 from getmaxun/revert-943-auto-robot

Revert "feat: add auto robots template"
This commit is contained in:
Karishma Shukla
2026-01-23 17:52:34 +05:30
committed by GitHub
5 changed files with 0 additions and 1374 deletions

View File

@@ -1,442 +0,0 @@
import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';
import {
Box,
Typography,
TextField,
Button,
Divider,
Chip,
FormControl,
InputLabel,
Select,
MenuItem,
Stack,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from '@mui/material';
import { GenericModal } from '../ui/GenericModal';
interface AutoRobotData {
id: string;
name: string;
category?: string;
description?: string;
access?: string;
sample?: any[];
logo?: string;
configOptions?: {
parameters?: Array<{
id: string;
label: string;
type: 'dropdown' | 'search' | 'url' | 'limit' | 'username' | 'path-segment';
required?: boolean;
placeholder?: string;
queryParam: string;
options?: Array<{ value: string; label: string; }>;
}>;
};
}
interface AutoRobotDetailModalProps {
open: boolean;
onClose: () => void;
robot: AutoRobotData | null;
onUseRobot: (robot: AutoRobotData, config?: { parameters?: { [key: string]: string } }) => void;
}
export const AutoRobotDetailModal: FC<AutoRobotDetailModalProps> = ({ open, onClose, robot, onUseRobot }) => {
const { t } = useTranslation();
if (!robot) return null;
const sampleData = robot.sample || [];
const columnHeaders = sampleData.length > 0
? Object.keys(sampleData[0])
: [];
const needsConfiguration = robot.configOptions?.parameters && robot.configOptions.parameters.length > 0;
const parameters = robot.configOptions?.parameters || [];
const handleUseRobot = () => {
onUseRobot(robot);
};
return (
<GenericModal
isOpen={open}
onClose={onClose}
modalStyle={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '800px',
maxWidth: '90vw',
height: 'auto',
maxHeight: '85vh',
padding: '28px 32px',
overflow: 'auto',
backgroundColor: 'background.paper',
boxShadow: '0px 8px 24px rgba(0, 0, 0, 0.15)',
borderRadius: '12px',
scrollbarWidth: 'thin',
scrollbarColor: 'rgba(128, 128, 128, 0.3) transparent'
}}
>
<Box sx={{ width: '100%' }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 3, mb: 3 }}>
{robot.logo && (
<Box
component="img"
src={robot.logo}
alt={`${robot.name} logo`}
sx={{
width: 56,
height: 56,
objectFit: 'contain',
flexShrink: 0,
mt: 0.5,
}}
/>
)}
<Box sx={{ flex: 1, minWidth: 0 }}>
<Typography
variant="h4"
component="h1"
sx={{
fontWeight: 600,
color: 'text.primary',
fontSize: '1.75rem',
lineHeight: 1.2,
mb: 1
}}
>
{robot.name}
</Typography>
<Chip
label={robot.category || t('recordingtable.no_category', 'Uncategorized')}
size="small"
color="primary"
variant="outlined"
sx={{
height: '28px',
fontSize: '0.8rem',
borderRadius: '14px',
fontWeight: 500
}}
/>
</Box>
</Box>
<Box
sx={{
mb: 4,
backgroundColor: 'action.hover',
borderRadius: '8px',
p: 3
}}
>
<Typography
variant="subtitle1"
sx={{
mb: 1,
fontWeight: 600,
color: 'text.primary',
display: 'flex',
alignItems: 'center',
gap: 1
}}
>
{t('robot.description', 'Description')}
</Typography>
<Typography
variant="body1"
sx={{
color: 'text.secondary',
lineHeight: 1.6
}}
>
{robot.description || t('recordingtable.no_description', 'No description available')}
</Typography>
</Box>
<Divider sx={{
my: 3,
borderColor: 'divider'
}} />
{needsConfiguration && (
<Box sx={{ mb: 4 }}>
<Typography
variant="subtitle1"
sx={{
mb: 2,
fontWeight: 600,
color: 'text.primary'
}}
>
{t('robot.config.configuration', 'Configuration')}
</Typography>
<Typography
variant="body2"
sx={{
mb: 2,
color: 'text.secondary',
fontStyle: 'italic'
}}
>
{t('robot.config.info_message', 'The following fields will be required when you use this robot:')}
</Typography>
<Stack spacing={3}>
{parameters.map((param) => {
if (param.type === 'dropdown') {
return (
<FormControl
key={param.id}
fullWidth
sx={{
opacity: 0.7,
pointerEvents: 'none'
}}
>
<InputLabel>{param.label}{param.required ? ' *' : ''}</InputLabel>
<Select
value=""
label={`${param.label}${param.required ? ' *' : ''}`}
readOnly
>
{param.options?.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
);
}
if (param.type === 'search') {
return (
<TextField
key={param.id}
fullWidth
label={`${param.label}${param.required ? ' *' : ''}`}
placeholder={param.placeholder}
value=""
InputProps={{ readOnly: true }}
sx={{
opacity: 0.7,
pointerEvents: 'none'
}}
/>
);
}
if (param.type === 'url') {
return (
<TextField
key={param.id}
fullWidth
type="url"
label={`${param.label}${param.required ? ' *' : ''}`}
placeholder={param.placeholder || 'https://example.com'}
value=""
InputProps={{ readOnly: true }}
sx={{
opacity: 0.7,
pointerEvents: 'none'
}}
/>
);
}
if (param.type === 'limit') {
return (
<TextField
key={param.id}
fullWidth
type="number"
label={`${param.label}${param.required ? ' *' : ''}`}
placeholder={param.placeholder || '100'}
value=""
InputProps={{ readOnly: true }}
sx={{
opacity: 0.7,
pointerEvents: 'none'
}}
/>
);
}
if (param.type === 'username') {
return (
<TextField
key={param.id}
fullWidth
label={`${param.label}${param.required ? ' *' : ''}`}
placeholder={param.placeholder || 'Enter username'}
value=""
InputProps={{ readOnly: true }}
sx={{
opacity: 0.7,
pointerEvents: 'none'
}}
/>
);
}
if (param.type === 'path-segment') {
return (
<TextField
key={param.id}
fullWidth
label={`${param.label}${param.required ? ' *' : ''}`}
placeholder={param.placeholder || 'Enter path segment value'}
value=""
InputProps={{ readOnly: true }}
sx={{
opacity: 0.7,
pointerEvents: 'none'
}}
/>
);
}
return null;
})}
</Stack>
</Box>
)}
{needsConfiguration && (
<Divider sx={{
my: 3,
borderColor: 'divider'
}} />
)}
{sampleData.length > 0 && (
<Box sx={{ mb: 4 }}>
<Typography
variant="subtitle1"
sx={{
mb: 2,
fontWeight: 600,
color: 'text.primary',
display: 'flex',
alignItems: 'center',
gap: 1
}}
>
{t('robot.sample_output', 'Sample Output')}
</Typography>
<Box
sx={{
width: '100%',
borderRadius: '4px',
overflow: 'auto'
}}
>
<Table size="medium" sx={{ width: '100%' }}>
<TableHead>
<TableRow sx={{ backgroundColor: '#242424' }}>
{columnHeaders.map((header, index) => (
<TableCell
key={index}
sx={{
fontWeight: 600,
color: 'white',
borderBottom: 'none',
py: 2,
px: 2,
whiteSpace: 'normal',
minWidth: '100px'
}}
>
{header}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{sampleData.map((row, rowIndex) => (
<TableRow
key={rowIndex}
sx={{
borderBottom: '1px solid',
borderColor: 'divider',
'&:last-child': {
borderBottom: 'none'
}
}}
>
{columnHeaders.map((header, cellIndex) => (
<TableCell
key={`${rowIndex}-${cellIndex}`}
sx={{
py: 2.5,
px: 2,
color: 'text.secondary',
fontWeight: 'inherit',
borderBottom: 'none',
whiteSpace: 'normal',
minWidth: '100px'
}}
>
{row[header] !== null ? (typeof row[header] === 'object' ? JSON.stringify(row[header]) : String(row[header])) : '-'}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</Box>
</Box>
)}
<Box sx={{
display: 'flex',
justifyContent: 'flex-end',
gap: 2,
mt: 5,
mb: 1
}}>
<Button
onClick={onClose}
variant="outlined"
sx={{
color: '#ff00c3 !important',
borderColor: '#ff00c3 !important',
backgroundColor: 'white !important',
}} >
{t('common.cancel', 'Cancel')}
</Button>
<Button
onClick={handleUseRobot}
variant="contained"
color="primary"
sx={{
borderRadius: '8px',
px: 3,
py: 1,
boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.1)',
textTransform: 'none',
fontWeight: 500
}}
>
{t('robot.add_to_my_robots', 'Use this robot')}
</Button>
</Box>
</Box>
</GenericModal>
);
};
export default AutoRobotDetailModal;

View File

@@ -1,406 +0,0 @@
import React, { memo, useState, useMemo, useEffect, FC } from "react";
import { useTranslation } from 'react-i18next';
import {
Box,
Typography,
TextField,
Card,
CardContent,
Grid,
InputAdornment,
Divider,
Chip,
Pagination,
Button,
} from "@mui/material";
import { Search as SearchIcon } from "@mui/icons-material";
import { GenericModal } from "../ui/GenericModal";
import { AutoRobotDetailModal } from "./AutoRobotDetailModal";
import { AUTO_ROBOTS } from "../../constants/autoRobots";
interface Data {
id: string;
name: string;
category?: string;
description?: string;
access?: string;
sample?: any[];
logo?: string;
configOptions?: {
parameters?: Array<{
id: string;
label: string;
type: 'dropdown' | 'search' | 'url' | 'limit' | 'username' | 'path-segment';
required?: boolean;
placeholder?: string;
queryParam: string;
options?: Array<{ value: string; label: string; }>;
}>;
};
}
interface RecordingCardProps {
row: Data;
onUseRobot: (robot: Data) => void;
}
const RecordingCard: FC<RecordingCardProps> = memo(({ row, onUseRobot }) => {
const { t } = useTranslation();
const isPremium = row.access === 'premium';
return (
<Card
sx={{
height: "100%",
display: 'flex',
flexDirection: 'column',
transition: 'all 0.2s ease-in-out',
'&:hover': {
transform: 'translateY(-4px)',
boxShadow: '0 4px 20px rgba(0,0,0,0.1)',
},
borderRadius: 2,
overflow: 'visible',
position: 'relative'
}}
>
{isPremium && (
<Chip
label="Premium"
size="small"
color="secondary"
sx={{
position: 'absolute',
top: 10,
right: 10,
backgroundColor: '#ff00c3',
color: 'white',
fontWeight: 'bold',
height: '24px',
fontSize: '0.7rem',
zIndex: 1
}}
/>
)}
<CardContent
sx={{
p: 3,
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between'
}}
>
<Box sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2, mb: 2 }}>
{row.logo && (
<Box
component="img"
src={row.logo}
alt={`${row.name} logo`}
sx={{
width: 48,
height: 48,
objectFit: 'contain',
flexShrink: 0,
mt: 0.25,
}}
/>
)}
<Box sx={{ flex: 1, minWidth: 0 }}>
<Typography
variant="h6"
component="h2"
sx={{
fontWeight: 600,
mb: 0.5,
color: theme => theme.palette.text.primary,
lineHeight: 1.2,
height: '2.4em',
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{row.name}
</Typography>
</Box>
</Box>
<Typography
variant="body2"
color="text.secondary"
sx={{
height: '4.5em',
display: '-webkit-box',
WebkitLineClamp: 3,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis',
marginTop: '18px',
fontSize: '16px',
lineHeight: 1.5
}}
>
{row.description || t('recordingtable.no_description')}
</Typography>
</Box>
<Box>
<Divider sx={{ mb: 2 }} />
<Button
variant="contained"
color="primary"
onClick={() => onUseRobot(row)}
fullWidth
sx={{
borderRadius: 1.5,
py: 1.2
}}
>
{t('robot.use_this_robot', 'Use this robot')}
</Button>
</Box>
</CardContent>
</Card>
);
});
export const AutoRobots: FC = () => {
const { t } = useTranslation();
const [page, setPage] = useState<number>(1);
const [rowsPerPage] = useState<number>(6);
const [searchTerm, setSearchTerm] = useState<string>('');
const [selectedCategory, setSelectedCategory] = useState<string>('');
const [selectedRobot, setSelectedRobot] = useState<Data | null>(null);
const [detailModalOpen, setDetailModalOpen] = useState<boolean>(false);
const [cloudModalOpen, setCloudModalOpen] = useState<boolean>(false);
const rows = AUTO_ROBOTS;
const categories = useMemo(() => {
const uniqueCategories = [...new Set(rows
.map((row: any) => row.category)
.filter(Boolean)
)];
return uniqueCategories;
}, [rows]);
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
const debouncedSearchTerm = useDebounce<string>(searchTerm, 300);
const filteredRows = useMemo<Data[]>(() => {
let filtered = rows;
if (selectedCategory) {
filtered = filtered.filter(row => row.category === selectedCategory);
}
const searchLower = debouncedSearchTerm.toLowerCase();
if (debouncedSearchTerm) {
filtered = filtered.filter(row => row.name.toLowerCase().includes(searchLower));
}
return filtered;
}, [rows, debouncedSearchTerm, selectedCategory]);
const handlePageChange = (_event: React.ChangeEvent<unknown>, value: number): void => {
setPage(value);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
const paginatedRows = useMemo<Data[]>(() => {
const startIndex = (page - 1) * rowsPerPage;
return filteredRows.slice(startIndex, startIndex + rowsPerPage);
}, [filteredRows, page, rowsPerPage]);
const totalPages = Math.ceil(filteredRows.length / rowsPerPage);
const handleUseRobotClick = (robot: Data): void => {
setSelectedRobot(robot);
setDetailModalOpen(true);
};
const handleAddToMyRobots = (robot: Data, config?: { parameters?: { [key: string]: string } }): void => {
setDetailModalOpen(false);
setCloudModalOpen(true);
};
return (
<>
<Box sx={{ padding: "30px" }}>
<Box sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
mb: 3
}}>
<Typography variant="h6">
{t('mainmenu.prebuilt_robots', 'Auto Robots')}
</Typography>
<TextField
size="small"
placeholder={t('recordingtable.search', 'Search')}
value={searchTerm}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
sx={{ width: '250px' }}
/>
</Box>
<Box sx={{
display: 'flex',
gap: 1,
mb: 3,
flexWrap: 'wrap'
}}>
<Chip
label="All"
onClick={() => setSelectedCategory('')}
color={selectedCategory === '' ? 'primary' : 'default'}
sx={{
'&:hover': { backgroundColor: theme => selectedCategory === '' ? theme.palette.primary.main : theme.palette.action.hover }
}}
/>
{categories.map((category) => (
<Chip
key={category}
label={category}
onClick={() => setSelectedCategory(category)}
color={selectedCategory === category ? 'primary' : 'default'}
sx={{
'&:hover': { backgroundColor: theme => selectedCategory === category ? theme.palette.primary.main : theme.palette.action.hover }
}}
/>
))}
</Box>
<Grid container spacing={3}>
{paginatedRows.map((row) => (
<Grid item xs={12} sm={6} md={4} key={row.id}>
<RecordingCard
row={row}
onUseRobot={handleUseRobotClick}
/>
</Grid>
))}
</Grid>
{filteredRows.length === 0 ? (
<Box sx={{
textAlign: 'center',
py: 8,
bgcolor: 'background.paper',
borderRadius: 1,
mt: 3
}}>
<Typography color="text.secondary">
{searchTerm
? t('recordingtable.no_results', 'No results found')
: t('recordingtable.no_recordings', 'No robots available')}
</Typography>
</Box>
) : (
<Box sx={{
display: 'flex',
justifyContent: 'center',
mt: 4,
mb: 2
}}>
<Pagination
count={totalPages}
page={page}
onChange={handlePageChange}
color="primary"
size="large"
showFirstButton
showLastButton
/>
</Box>
)}
</Box>
<AutoRobotDetailModal
open={detailModalOpen}
onClose={() => setDetailModalOpen(false)}
robot={selectedRobot}
onUseRobot={handleAddToMyRobots}
/>
<GenericModal
isOpen={cloudModalOpen}
onClose={() => setCloudModalOpen(false)}
modalStyle={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '500px',
maxWidth: '90vw',
padding: '32px',
backgroundColor: 'background.paper',
boxShadow: '0px 8px 24px rgba(0, 0, 0, 0.15)',
borderRadius: '12px',
}}
>
<Box>
<Typography variant="h5" fontWeight={600} mb={2}>
Available on Maxun Cloud
</Typography>
<Typography variant="body1" color="text.secondary" mb={3}>
Auto Robots are available exclusively on Maxun Cloud. Sign up for free to access pre-built automation templates and start extracting data instantly.
</Typography>
<Box display="flex" gap={2}>
<Button
variant="contained"
color="primary"
fullWidth
onClick={() => window.open('https://app.maxun.dev/prebuilt-robots', '_blank', 'noopener,noreferrer')}
>
Go to Maxun Cloud
</Button>
<Button
variant="outlined"
color="primary"
fullWidth
onClick={() => setCloudModalOpen(false)}
sx={{
color: '#ff00c3 !important',
borderColor: '#ff00c3 !important',
}}
>
Cancel
</Button>
</Box>
</Box>
</GenericModal>
</>
);
};
export default AutoRobots;