mirror of
https://github.com/jaypyles/Scraperr.git
synced 2025-12-14 11:46:17 +00:00
wip: update UI
This commit is contained in:
10
README.md
10
README.md
@@ -13,12 +13,16 @@ From the table, users can download an excel sheet of the job's results, along wi
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
### Submitting URLs for Scraping
|
||||||
|
|
||||||
- Submit/Queue URLs for web scraping
|
- Submit/Queue URLs for web scraping
|
||||||
- Add and manage elements to scrape using XPath
|
- Add and manage elements to scrape using XPath
|
||||||
- Scrape all pages within same domain
|
- Scrape all pages within same domain
|
||||||
- Add custom json headers to send in requests to URLs
|
- Add custom json headers to send in requests to URLs
|
||||||
- Display results of scraped data
|
- Display results of scraped data
|
||||||
|
|
||||||
|
### Managing Previous Jobs
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- Download csv containing results
|
- Download csv containing results
|
||||||
@@ -28,14 +32,20 @@ From the table, users can download an excel sheet of the job's results, along wi
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### User Management
|
||||||
|
|
||||||
- User login/signup to organize jobs
|
- User login/signup to organize jobs
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Log Viewing
|
||||||
|
|
||||||
- View app logs inside of web ui
|
- View app logs inside of web ui
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Statistics View
|
||||||
|
|
||||||
- View a small statistics view of jobs ran
|
- View a small statistics view of jobs ran
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -57,6 +57,7 @@ app.add_middleware(
|
|||||||
)
|
)
|
||||||
|
|
||||||
app.mount("/_next/static", StaticFiles(directory="./dist/_next/static"), name="static")
|
app.mount("/_next/static", StaticFiles(directory="./dist/_next/static"), name="static")
|
||||||
|
app.mount("/images", StaticFiles(directory="./dist/images"), name="images")
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
container_name: scraperr
|
container_name: scraperr
|
||||||
|
ports:
|
||||||
|
- 9000:8000
|
||||||
env_file:
|
env_file:
|
||||||
- ./.env
|
- ./.env
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 62 KiB |
@@ -2,6 +2,7 @@
|
|||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: "export",
|
output: "export",
|
||||||
distDir: "./dist",
|
distDir: "./dist",
|
||||||
|
images: { unoptimized: true },
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 454 B After Width: | Height: | Size: 4.2 KiB |
4
public/favicon.ico:Zone.Identifier
Normal file
4
public/favicon.ico:Zone.Identifier
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[ZoneTransfer]
|
||||||
|
ZoneId=3
|
||||||
|
ReferrerUrl=https://cloudconvert.com/
|
||||||
|
HostUrl=https://us-east.storage.cloudconvert.com/tasks/ff0a6031-1745-4e41-871e-35f102bfe11b/scraperr_logo.ico?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=cloudconvert-production%2F20240722%2Fva%2Fs3%2Faws4_request&X-Amz-Date=20240722T165500Z&X-Amz-Expires=86400&X-Amz-Signature=df96cece92026dc08d5b1dbfc2391beffa5101137fade6587625c467f088feb7&X-Amz-SignedHeaders=host&response-content-disposition=attachment%3B%20filename%3D%22scraperr_logo.ico%22&response-content-type=image%2Fvnd.microsoft.icon&x-id=GetObject
|
||||||
BIN
public/images/scraperr_logo.png
Normal file
BIN
public/images/scraperr_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
@@ -32,6 +32,7 @@ const COLOR_MAP: ColorMap = {
|
|||||||
Queued: "rgba(255,201,5,0.25)",
|
Queued: "rgba(255,201,5,0.25)",
|
||||||
Scraping: "rgba(3,104,255,0.25)",
|
Scraping: "rgba(3,104,255,0.25)",
|
||||||
Completed: "rgba(5,255,51,0.25)",
|
Completed: "rgba(5,255,51,0.25)",
|
||||||
|
Failed: "rgba(214,0,25,0.25)",
|
||||||
};
|
};
|
||||||
|
|
||||||
const JobTable: React.FC<JobTableProps> = ({ jobs, fetchJobs }) => {
|
const JobTable: React.FC<JobTableProps> = ({ jobs, fetchJobs }) => {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import TerminalIcon from "@mui/icons-material/Terminal";
|
|||||||
import BarChart from "@mui/icons-material/BarChart";
|
import BarChart from "@mui/icons-material/BarChart";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
interface NavDrawerProps {
|
interface NavDrawerProps {
|
||||||
toggleTheme: () => void;
|
toggleTheme: () => void;
|
||||||
@@ -112,11 +113,9 @@ const NavDrawer: React.FC<NavDrawerProps> = ({ toggleTheme, isDarkMode }) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
|
||||||
onClick={logout}
|
onClick={logout}
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
color: theme.palette.mode === "light" ? "#000000" : "#ffffff",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Logout
|
Logout
|
||||||
@@ -125,24 +124,22 @@ const NavDrawer: React.FC<NavDrawerProps> = ({ toggleTheme, isDarkMode }) => {
|
|||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
|
||||||
onClick={() => router.push("/login")}
|
onClick={() => router.push("/login")}
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
color: theme.palette.mode === "light" ? "#000000" : "#ffffff",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Divider sx={{ marginTop: 2, marginBottom: 2 }}></Divider>
|
<Divider sx={{ marginTop: 2, marginBottom: 2 }}></Divider>
|
||||||
<Accordion sx={{ padding: 0, width: "90%" }}>
|
<Accordion sx={{ padding: 0, width: "90%", marginBottom: 1 }}>
|
||||||
<AccordionSummary
|
<AccordionSummary
|
||||||
expandIcon={<ExpandMoreIcon />}
|
expandIcon={<ExpandMoreIcon />}
|
||||||
aria-controls="panel1a-content"
|
aria-controls="panel1a-content"
|
||||||
id="panel1a-header"
|
id="panel1a-header"
|
||||||
>
|
>
|
||||||
<Typography>Settings</Typography>
|
<Typography>Quick Settings</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<div className="flex flex-row mr-1">
|
<div className="flex flex-row mr-1">
|
||||||
|
|||||||
@@ -136,31 +136,37 @@ export const JobQueue = ({
|
|||||||
{new Date(row.time_created).toLocaleString()}
|
{new Date(row.time_created).toLocaleString()}
|
||||||
</Box>
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell sx={{ maxWidth: 150, overflow: "auto" }}>
|
<TableCell sx={{ maxWidth: 50, overflow: "auto" }}>
|
||||||
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
||||||
<Box
|
<Box
|
||||||
className="rounded-md p-2 text-center"
|
className="rounded-md p-2 text-center"
|
||||||
sx={{ bgcolor: colors[row.status], opactity: "50%" }}
|
sx={{ bgcolor: colors[row.status] }}
|
||||||
>
|
>
|
||||||
{row.status}
|
{row.status}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell sx={{ maxWidth: 100, overflow: "auto" }}>
|
<TableCell sx={{ maxWidth: 150, overflow: "auto" }}>
|
||||||
<Button
|
<Box sx={{ display: "flex", gap: 1 }}>
|
||||||
onClick={() => {
|
<Button
|
||||||
onDownload([row.id]);
|
onClick={() => {
|
||||||
}}
|
onDownload([row.id]);
|
||||||
>
|
}}
|
||||||
Download
|
size="small"
|
||||||
</Button>
|
sx={{ minWidth: 0, padding: "4px 8px" }}
|
||||||
<Button
|
>
|
||||||
onClick={() =>
|
Download
|
||||||
onNavigate(row.elements, row.url, row.job_options)
|
</Button>
|
||||||
}
|
<Button
|
||||||
>
|
onClick={() =>
|
||||||
Rerun
|
onNavigate(row.elements, row.url, row.job_options)
|
||||||
</Button>
|
}
|
||||||
|
size="small"
|
||||||
|
sx={{ minWidth: 0, padding: "4px 8px" }}
|
||||||
|
>
|
||||||
|
Rerun
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const ElementTable = ({ rows, setRows, submittedURL }: Props) => {
|
|||||||
|
|
||||||
const handleAddRow = () => {
|
const handleAddRow = () => {
|
||||||
const updatedRow = { ...newRow, url: submittedURL };
|
const updatedRow = { ...newRow, url: submittedURL };
|
||||||
setRows([...rows, updatedRow]);
|
setRows([updatedRow, ...rows]);
|
||||||
setNewRow({ name: "", xpath: "", url: "" });
|
setNewRow({ name: "", xpath: "", url: "" });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -44,87 +44,126 @@ export const ElementTable = ({ rows, setRows, submittedURL }: Props) => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box className="animate-fadeIn p-2" bgcolor="background.paper">
|
||||||
<Box display="flex" gap={2} marginBottom={2} className="items-center">
|
<Box className="text-center mb-4">
|
||||||
<TextField
|
<Typography variant="h4" sx={{ marginBottom: 1 }}>
|
||||||
label="Name"
|
Elements to Scrape
|
||||||
variant="outlined"
|
</Typography>
|
||||||
fullWidth
|
<TableContainer
|
||||||
value={newRow.name}
|
component={Box}
|
||||||
onChange={(e) => setNewRow({ ...newRow, name: e.target.value })}
|
sx={{ maxHeight: "50%", overflow: "auto" }}
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
label="XPath"
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
value={newRow.xpath}
|
|
||||||
onChange={(e) => setNewRow({ ...newRow, xpath: e.target.value })}
|
|
||||||
/>
|
|
||||||
<Tooltip
|
|
||||||
title={
|
|
||||||
newRow.xpath.length > 0 && newRow.name.length > 0
|
|
||||||
? "Add Element"
|
|
||||||
: "Fill out all fields to add an element"
|
|
||||||
}
|
|
||||||
placement="top"
|
|
||||||
>
|
>
|
||||||
<span>
|
<div className="rounded-lg shadow-md border border-gray-300 overflow-hidden">
|
||||||
<IconButton
|
<Table
|
||||||
aria-label="add"
|
stickyHeader
|
||||||
size="small"
|
className="mb-4"
|
||||||
onClick={handleAddRow}
|
|
||||||
sx={{
|
sx={{
|
||||||
height: "40px",
|
tableLayout: "fixed",
|
||||||
width: "40px",
|
width: "100%",
|
||||||
|
"& .MuiTableCell-root": {
|
||||||
|
borderBottom: "1px solid #e0e0e0",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
disabled={!(newRow.xpath.length > 0 && newRow.name.length > 0)}
|
|
||||||
>
|
>
|
||||||
<AddIcon
|
<TableHead>
|
||||||
fontSize="inherit"
|
<TableRow>
|
||||||
sx={{
|
<TableCell>
|
||||||
color: theme.palette.mode === "light" ? "#000000" : "#ffffff",
|
<Typography sx={{ fontWeight: "bold" }}>Name</Typography>
|
||||||
}}
|
</TableCell>
|
||||||
/>
|
<TableCell>
|
||||||
</IconButton>
|
<Typography sx={{ fontWeight: "bold" }}>XPath</Typography>
|
||||||
</span>
|
</TableCell>
|
||||||
</Tooltip>
|
<TableCell>
|
||||||
|
<Typography sx={{ fontWeight: "bold" }}>Actions</Typography>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<TextField
|
||||||
|
label="Name"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
value={newRow.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNewRow({ ...newRow, name: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<TextField
|
||||||
|
label="XPath"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
value={newRow.xpath}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNewRow({ ...newRow, xpath: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
newRow.xpath.length > 0 && newRow.name.length > 0
|
||||||
|
? "Add Element"
|
||||||
|
: "Fill out all fields to add an element"
|
||||||
|
}
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
aria-label="add"
|
||||||
|
size="small"
|
||||||
|
onClick={handleAddRow}
|
||||||
|
sx={{
|
||||||
|
height: "40px",
|
||||||
|
width: "40px",
|
||||||
|
}}
|
||||||
|
disabled={
|
||||||
|
!(newRow.xpath.length > 0 && newRow.name.length > 0)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AddIcon
|
||||||
|
fontSize="inherit"
|
||||||
|
sx={{
|
||||||
|
color:
|
||||||
|
theme.palette.mode === "light"
|
||||||
|
? "#000000"
|
||||||
|
: "#ffffff",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
{rows.map((row, index) => (
|
||||||
|
<TableRow key={index}>
|
||||||
|
<TableCell>
|
||||||
|
<Typography>{row.name}</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Typography>{row.xpath}</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button
|
||||||
|
onClick={() => handleDeleteRow(row.name)}
|
||||||
|
className="!bg-red-500 bg-opacity-50 !text-white font-semibold rounded-md
|
||||||
|
transition-transform transform hover:scale-105 hover:bg-red-500"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</TableContainer>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h4">Elements</Typography>
|
</Box>
|
||||||
<TableContainer
|
|
||||||
component={Box}
|
|
||||||
sx={{ maxHeight: "50%", overflow: "auto" }}
|
|
||||||
>
|
|
||||||
<Table stickyHeader className="mb-4">
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<Typography sx={{ fontWeight: "bold" }}>Name</Typography>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Typography sx={{ fontWeight: "bold" }}>XPath</Typography>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{rows.map((row, index) => (
|
|
||||||
<TableRow key={index}>
|
|
||||||
<TableCell>
|
|
||||||
<Typography>{row.name}</Typography>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Typography>{row.xpath}</Typography>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Button onClick={() => handleDeleteRow(row.name)}>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ interface StateProps {
|
|||||||
submittedURL: string;
|
submittedURL: string;
|
||||||
setSubmittedURL: Dispatch<React.SetStateAction<string>>;
|
setSubmittedURL: Dispatch<React.SetStateAction<string>>;
|
||||||
rows: Element[];
|
rows: Element[];
|
||||||
setResults: Dispatch<React.SetStateAction<Result>>;
|
isValidURL: boolean;
|
||||||
|
setIsValidUrl: Dispatch<React.SetStateAction<boolean>>;
|
||||||
setSnackbarMessage: Dispatch<React.SetStateAction<string>>;
|
setSnackbarMessage: Dispatch<React.SetStateAction<string>>;
|
||||||
setSnackbarOpen: Dispatch<React.SetStateAction<boolean>>;
|
setSnackbarOpen: Dispatch<React.SetStateAction<boolean>>;
|
||||||
setSnackbarSeverity: Dispatch<React.SetStateAction<string>>;
|
setSnackbarSeverity: Dispatch<React.SetStateAction<string>>;
|
||||||
@@ -40,13 +41,13 @@ export const JobSubmitter = ({ stateProps }: Props) => {
|
|||||||
submittedURL,
|
submittedURL,
|
||||||
setSubmittedURL,
|
setSubmittedURL,
|
||||||
rows,
|
rows,
|
||||||
setResults,
|
isValidURL,
|
||||||
|
setIsValidUrl,
|
||||||
setSnackbarMessage,
|
setSnackbarMessage,
|
||||||
setSnackbarOpen,
|
setSnackbarOpen,
|
||||||
setSnackbarSeverity,
|
setSnackbarSeverity,
|
||||||
} = stateProps;
|
} = stateProps;
|
||||||
|
|
||||||
const [isValidURL, setIsValidUrl] = useState<boolean>(true);
|
|
||||||
const [urlError, setUrlError] = useState<string | null>(null);
|
const [urlError, setUrlError] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [jobOptions, setJobOptions] = useState<JobOptions>({
|
const [jobOptions, setJobOptions] = useState<JobOptions>({
|
||||||
@@ -157,15 +158,17 @@ export const JobSubmitter = ({ stateProps }: Props) => {
|
|||||||
onChange={(e) => setSubmittedURL(e.target.value)}
|
onChange={(e) => setSubmittedURL(e.target.value)}
|
||||||
error={!isValidURL}
|
error={!isValidURL}
|
||||||
helperText={!isValidURL ? urlError : ""}
|
helperText={!isValidURL ? urlError : ""}
|
||||||
|
className="rounded-md"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
|
||||||
size="small"
|
size="small"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={!(rows.length > 0) || loading}
|
disabled={!(rows.length > 0) || loading}
|
||||||
|
className={`bg-gradient-to-r from-[#034efc] to-gray-500 text-white font-semibold rounded-md
|
||||||
|
transition-transform transform hover:scale-105 disabled:opacity-50`}
|
||||||
>
|
>
|
||||||
{loading ? <CircularProgress size={24} /> : "Submit"}
|
{loading ? <CircularProgress size={24} color="inherit" /> : "Submit"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Box bgcolor="background.paper" className="flex flex-col mb-2 rounded-md">
|
<Box bgcolor="background.paper" className="flex flex-col mb-2 rounded-md">
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const Home = () => {
|
|||||||
const [snackbarOpen, setSnackbarOpen] = useState<boolean>(false);
|
const [snackbarOpen, setSnackbarOpen] = useState<boolean>(false);
|
||||||
const [snackbarMessage, setSnackbarMessage] = useState<string>("");
|
const [snackbarMessage, setSnackbarMessage] = useState<string>("");
|
||||||
const [snackbarSeverity, setSnackbarSeverity] = useState<string>("error");
|
const [snackbarSeverity, setSnackbarSeverity] = useState<string>("error");
|
||||||
|
const [isValidURL, setIsValidUrl] = useState<boolean>(true);
|
||||||
|
|
||||||
const resultsRef = useRef<HTMLTableElement | null>(null);
|
const resultsRef = useRef<HTMLTableElement | null>(null);
|
||||||
|
|
||||||
@@ -74,26 +75,26 @@ const Home = () => {
|
|||||||
minHeight="100vh"
|
minHeight="100vh"
|
||||||
py={4}
|
py={4}
|
||||||
>
|
>
|
||||||
<Container maxWidth="md">
|
<Container maxWidth="lg">
|
||||||
<Typography variant="h1" gutterBottom textAlign="center">
|
|
||||||
Scraperr
|
|
||||||
</Typography>
|
|
||||||
<JobSubmitter
|
<JobSubmitter
|
||||||
stateProps={{
|
stateProps={{
|
||||||
submittedURL,
|
submittedURL,
|
||||||
setSubmittedURL,
|
setSubmittedURL,
|
||||||
rows,
|
rows,
|
||||||
setResults,
|
isValidURL,
|
||||||
|
setIsValidUrl,
|
||||||
setSnackbarMessage,
|
setSnackbarMessage,
|
||||||
setSnackbarOpen,
|
setSnackbarOpen,
|
||||||
setSnackbarSeverity,
|
setSnackbarSeverity,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ElementTable
|
{submittedURL.length ? (
|
||||||
rows={rows}
|
<ElementTable
|
||||||
setRows={setRows}
|
rows={rows}
|
||||||
submittedURL={submittedURL}
|
setRows={setRows}
|
||||||
/>
|
submittedURL={submittedURL}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</Container>
|
</Container>
|
||||||
{snackbarSeverity === "info" ? <NotifySnackbar /> : <ErrorSnackbar />}
|
{snackbarSeverity === "info" ? <NotifySnackbar /> : <ErrorSnackbar />}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -70,7 +70,24 @@ const lightTheme = createTheme({
|
|||||||
secondary: "#333333",
|
secondary: "#333333",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
...commonThemeOptions,
|
...commonThemeOptions,
|
||||||
|
components: {
|
||||||
|
...commonThemeOptions.components,
|
||||||
|
MuiButton: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
color: "white",
|
||||||
|
"&.MuiButton-root": {
|
||||||
|
backgroundColor: "#034efc",
|
||||||
|
},
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#027be0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const darkTheme = createTheme({
|
const darkTheme = createTheme({
|
||||||
@@ -132,6 +149,12 @@ const darkTheme = createTheme({
|
|||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: {
|
root: {
|
||||||
color: "white",
|
color: "white",
|
||||||
|
"&.MuiButton-root": {
|
||||||
|
backgroundColor: "#034efc",
|
||||||
|
},
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#027be0",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,23 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
||||||
theme: {},
|
theme: {
|
||||||
|
extend: {
|
||||||
|
animation: {
|
||||||
|
fadeIn: "fadeIn 0.5s ease-in-out",
|
||||||
|
fadeOut: "fadeOut 0.5s ease-in-out",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
fadeIn: {
|
||||||
|
"0%": { opacity: 0 },
|
||||||
|
"100%": { opacity: 1 },
|
||||||
|
},
|
||||||
|
fadeOut: {
|
||||||
|
"0%": { opacity: 1 },
|
||||||
|
"100%": { opacity: 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user