Updated PosterSelector and UploadDiary components
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
|||||||
import Pagination from "@mui/material/Pagination";
|
import Pagination from "@mui/material/Pagination";
|
||||||
import { createTheme, ThemeProvider } from "@mui/material/styles";
|
import { createTheme, ThemeProvider } from "@mui/material/styles";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
import pulpGif from "../static/images/pulp.gif";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
@@ -25,11 +26,15 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
padding: theme.spacing(3),
|
padding: theme.spacing(3),
|
||||||
borderRadius: theme.shape.borderRadius,
|
borderRadius: theme.shape.borderRadius,
|
||||||
backgroundColor: "#1c1f23",
|
backgroundColor: "#1c1f23",
|
||||||
|
color: "white",
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
color: "white",
|
|
||||||
marginBottom: theme.spacing(3),
|
marginBottom: theme.spacing(3),
|
||||||
},
|
},
|
||||||
|
username: {
|
||||||
|
color: "#00A346",
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
list: {
|
list: {
|
||||||
maxHeight: "800px",
|
maxHeight: "800px",
|
||||||
minHeight: "1000px",
|
minHeight: "1000px",
|
||||||
@@ -94,6 +99,44 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
color: "white",
|
color: "white",
|
||||||
},
|
},
|
||||||
|
noMoviesContainer: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
height: "400px",
|
||||||
|
},
|
||||||
|
gifContainer: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
position: "relative",
|
||||||
|
width: "200px",
|
||||||
|
height: "200px",
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
|
circleBackground: {
|
||||||
|
position: "absolute",
|
||||||
|
width: "120%",
|
||||||
|
height: "120%",
|
||||||
|
borderRadius: "50%",
|
||||||
|
backgroundColor: "#ffffff",
|
||||||
|
clipPath: "inset(0 0 25% 0)",
|
||||||
|
bottom: "-28%",
|
||||||
|
},
|
||||||
|
gif: {
|
||||||
|
position: "absolute",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "contain",
|
||||||
|
},
|
||||||
|
backButton: {
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
backgroundColor: "#00A346",
|
||||||
|
color: "white",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#008036",
|
||||||
|
},
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const paginationTheme = createTheme({
|
const paginationTheme = createTheme({
|
||||||
@@ -134,6 +177,7 @@ const PosterSelector = () => {
|
|||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [totalPages, setTotalPages] = useState(1);
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const moviesPerPage = 10;
|
const moviesPerPage = 10;
|
||||||
@@ -143,8 +187,18 @@ const PosterSelector = () => {
|
|||||||
const pageNumber = parsed.page ? parseInt(parsed.page, 10) : 1;
|
const pageNumber = parsed.page ? parseInt(parsed.page, 10) : 1;
|
||||||
setPage(pageNumber);
|
setPage(pageNumber);
|
||||||
fetchMovies(pageNumber);
|
fetchMovies(pageNumber);
|
||||||
|
fetchUsername();
|
||||||
}, [location.search]);
|
}, [location.search]);
|
||||||
|
|
||||||
|
const fetchUsername = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get("http://localhost:5000/api/username");
|
||||||
|
setUsername(response.data.username);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching username:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchMovies = async (pageNumber) => {
|
const fetchMovies = async (pageNumber) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -195,6 +249,10 @@ const PosterSelector = () => {
|
|||||||
navigate(`?page=${value}`);
|
navigate(`?page=${value}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleGoBack = () => {
|
||||||
|
navigate("/");
|
||||||
|
};
|
||||||
|
|
||||||
const formatWatchedDate = (dateString) => {
|
const formatWatchedDate = (dateString) => {
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
const day = date.getDate().toString().padStart(2, "0");
|
const day = date.getDate().toString().padStart(2, "0");
|
||||||
@@ -210,6 +268,11 @@ const PosterSelector = () => {
|
|||||||
<Typography variant="h4" gutterBottom className={classes.title}>
|
<Typography variant="h4" gutterBottom className={classes.title}>
|
||||||
Your diary
|
Your diary
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{username && (
|
||||||
|
<Typography variant="h6" className={classes.username}>
|
||||||
|
Letterboxd User: {username}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Box className={classes.progressContainer}>
|
<Box className={classes.progressContainer}>
|
||||||
<LinearProgress variant="determinate" value={progress} />
|
<LinearProgress variant="determinate" value={progress} />
|
||||||
@@ -218,93 +281,111 @@ const PosterSelector = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
) : movies.length > 0 ? (
|
) : movies.length > 0 ? (
|
||||||
<List className={classes.list}>
|
<>
|
||||||
{movies.map((movie, index) => (
|
<List className={classes.list}>
|
||||||
<ListItem
|
{movies.map((movie, index) => (
|
||||||
button
|
<ListItem
|
||||||
key={index}
|
button
|
||||||
onClick={() => handleMovieClick(movie.Name, movie.Year)}
|
key={index}
|
||||||
className={classes.listItem}
|
onClick={() => handleMovieClick(movie.Name, movie.Year)}
|
||||||
>
|
className={classes.listItem}
|
||||||
<Grid container alignItems="center">
|
>
|
||||||
<Grid item>
|
<Grid container alignItems="center">
|
||||||
<img
|
<Grid item>
|
||||||
src={
|
<img
|
||||||
`https://image.tmdb.org/t/p/w500${movie.Poster.file_path}` ||
|
src={
|
||||||
`/api/placeholder/50/75`
|
`https://image.tmdb.org/t/p/w500${movie.Poster.file_path}` ||
|
||||||
}
|
`/api/placeholder/50/75`
|
||||||
alt={movie.Name}
|
}
|
||||||
className={classes.poster}
|
alt={movie.Name}
|
||||||
/>
|
className={classes.poster}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item className={classes.movieInfo}>
|
||||||
|
<Typography
|
||||||
|
variant="subtitle1"
|
||||||
|
className={classes.movieTitle}
|
||||||
|
>
|
||||||
|
{movie.Name}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="textSecondary"
|
||||||
|
className={classes.movieYear}
|
||||||
|
>
|
||||||
|
{movie.Year}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Box className={classes.watchedDate}>
|
||||||
|
{(() => {
|
||||||
|
const { day, month } = formatWatchedDate(
|
||||||
|
movie["Watched Date"]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
className={classes.watchedDay}
|
||||||
|
>
|
||||||
|
{day}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
className={classes.watchedMonth}
|
||||||
|
>
|
||||||
|
{month}
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item className={classes.movieInfo}>
|
</ListItem>
|
||||||
<Typography
|
))}
|
||||||
variant="subtitle1"
|
</List>
|
||||||
className={classes.movieTitle}
|
<Box className={classes.paginationContainer}>
|
||||||
>
|
<ThemeProvider theme={paginationTheme}>
|
||||||
{movie.Name}
|
<Pagination
|
||||||
</Typography>
|
count={totalPages}
|
||||||
<Typography
|
page={page}
|
||||||
variant="body2"
|
onChange={handlePageChange}
|
||||||
color="textSecondary"
|
color="primary"
|
||||||
className={classes.movieYear}
|
/>
|
||||||
>
|
</ThemeProvider>
|
||||||
{movie.Year}
|
</Box>
|
||||||
</Typography>
|
<Button
|
||||||
</Grid>
|
variant="contained"
|
||||||
<Grid item>
|
className={classes.refreshButton}
|
||||||
<Box className={classes.watchedDate}>
|
onClick={handleRefreshFiles}
|
||||||
{(() => {
|
>
|
||||||
const { day, month } = formatWatchedDate(
|
Refresh File
|
||||||
movie["Watched Date"]
|
</Button>
|
||||||
);
|
</>
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
className={classes.watchedDay}
|
|
||||||
>
|
|
||||||
{day}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
className={classes.watchedMonth}
|
|
||||||
>
|
|
||||||
{month}
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
) : (
|
) : (
|
||||||
<Box>
|
<Box className={classes.noMoviesContainer}>
|
||||||
<Typography variant="body1" color="textSecondary" gutterBottom>
|
<div className={classes.gifContainer}>
|
||||||
No movies to display. Upload a CSV file to get started.
|
<div className={classes.circleBackground}></div>
|
||||||
|
<img
|
||||||
|
src={pulpGif}
|
||||||
|
alt="Confused reaction"
|
||||||
|
className={classes.gif}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Typography variant="body1" gutterBottom>
|
||||||
|
No movies found in your diary. Please check your Letterboxd
|
||||||
|
username or CSV file.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
className={classes.backButton}
|
||||||
|
onClick={handleGoBack}
|
||||||
|
>
|
||||||
|
Go Back
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Box className={classes.paginationContainer}>
|
|
||||||
<ThemeProvider theme={paginationTheme}>
|
|
||||||
<Pagination
|
|
||||||
count={totalPages}
|
|
||||||
page={page}
|
|
||||||
onChange={handlePageChange}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</ThemeProvider>
|
|
||||||
</Box>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
className={classes.refreshButton}
|
|
||||||
onClick={handleRefreshFiles}
|
|
||||||
>
|
|
||||||
Refresh File
|
|
||||||
</Button>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
Button,
|
Button,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
|
TextField,
|
||||||
makeStyles,
|
makeStyles,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { CloudUploadOutlined } from "@material-ui/icons";
|
import { CloudUploadOutlined } from "@material-ui/icons";
|
||||||
@@ -81,12 +82,31 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
marginTop: theme.spacing(4),
|
marginTop: theme.spacing(4),
|
||||||
padding: theme.spacing(3),
|
padding: theme.spacing(3),
|
||||||
},
|
},
|
||||||
|
usernameField: {
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
"& .MuiOutlinedInput-root": {
|
||||||
|
color: "#ffffff",
|
||||||
|
"& fieldset": {
|
||||||
|
borderColor: "#00A346",
|
||||||
|
},
|
||||||
|
"&:hover fieldset": {
|
||||||
|
borderColor: "#00A346",
|
||||||
|
},
|
||||||
|
"&.Mui-focused fieldset": {
|
||||||
|
borderColor: "#1caff2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"& .MuiInputLabel-root": {
|
||||||
|
color: "#ffffff",
|
||||||
|
},
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const UploadDiary = () => {
|
const UploadDiary = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [file, setFile] = useState(null);
|
const [file, setFile] = useState(null);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -112,21 +132,29 @@ const UploadDiary = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleUpload = async () => {
|
const handleUpload = async () => {
|
||||||
if (file) {
|
if (username || file) {
|
||||||
setUploading(true);
|
setUploading(true);
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
if (username) {
|
||||||
formData.append("file", file);
|
// Here you would implement the logic to fetch the diary from Letterboxd
|
||||||
|
// For now, we'll just simulate a successful fetch
|
||||||
|
console.log(`Fetching diary for user: ${username}`);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate API call
|
||||||
|
navigate("/PosterSelector");
|
||||||
|
} else if (file) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
|
||||||
await axios.post("http://localhost:5000/api/upload-csv", formData, {
|
await axios.post("http://localhost:5000/api/upload-csv", formData, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "multipart/form-data",
|
"Content-Type": "multipart/form-data",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
navigate("/PosterSelector");
|
navigate("/PosterSelector");
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error uploading file:", error);
|
console.error("Error processing diary:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
}
|
}
|
||||||
@@ -137,7 +165,22 @@ const UploadDiary = () => {
|
|||||||
<Container className={classes.root}>
|
<Container className={classes.root}>
|
||||||
<div className={classes.card}>
|
<div className={classes.card}>
|
||||||
<Typography variant="h4" className={classes.title}>
|
<Typography variant="h4" className={classes.title}>
|
||||||
Upload CSV File
|
Upload Letterboxd Diary
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
className={classes.usernameField}
|
||||||
|
label="Letterboxd Username"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
placeholder="Enter your Letterboxd username"
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
style={{ color: "#ffffff", marginBottom: "1rem" }}
|
||||||
|
>
|
||||||
|
Or upload a CSV file:
|
||||||
</Typography>
|
</Typography>
|
||||||
<div {...getRootProps()} className={classes.dropzone}>
|
<div {...getRootProps()} className={classes.dropzone}>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
@@ -163,41 +206,37 @@ const UploadDiary = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{file && (
|
<Grid container justify="center" className={classes.progressContainer}>
|
||||||
<Grid
|
{uploading ? (
|
||||||
container
|
<CircularProgress />
|
||||||
justify="center"
|
) : (
|
||||||
className={classes.progressContainer}
|
<Button
|
||||||
>
|
className={classes.submitButton}
|
||||||
{uploading ? (
|
variant="contained"
|
||||||
<CircularProgress />
|
onClick={handleUpload}
|
||||||
) : (
|
disabled={!username && !file}
|
||||||
<Button
|
>
|
||||||
className={classes.submitButton}
|
SUBMIT
|
||||||
variant="contained"
|
</Button>
|
||||||
onClick={handleUpload}
|
)}
|
||||||
>
|
</Grid>
|
||||||
SUBMIT
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<Typography variant="body1" className={classes.overviewText}>
|
<Typography variant="body1" className={classes.overviewText}>
|
||||||
This tool allows you to easily upload your diary.csv file from
|
This tool allows you to easily import your Letterboxd diary and select
|
||||||
Letterboxd and select your favorite movie poster. Follow these simple
|
your favorite movie poster. Follow these simple steps:
|
||||||
steps:
|
|
||||||
<br />
|
<br />
|
||||||
<strong>1. Upload Your CSV File:</strong> Drag and drop your diary.csv
|
<strong>1. Enter Your Letterboxd Username:</strong> Type your Letterboxd
|
||||||
file or click to select it from your device.
|
username to automatically fetch your diary.
|
||||||
<br />
|
<br />
|
||||||
<strong>2. Automatic Processing:</strong> Once uploaded, the application
|
<strong>2. Or Upload Your CSV File:</strong> If you prefer, you can
|
||||||
will automatically process the file.
|
still upload your diary.csv file directly.
|
||||||
<br />
|
<br />
|
||||||
<strong>3. Poster Selection:</strong> After the file is successfully
|
<strong>3. Automatic Processing:</strong> Once submitted, the
|
||||||
uploaded, you'll be redirected to the Poster Selector page, where you
|
application will process your diary.
|
||||||
can browse and choose your favorite movie poster based on your
|
<br />
|
||||||
Letterboxd diary.
|
<strong>4. Poster Selection:</strong> After processing, you'll be
|
||||||
|
redirected to the Poster Selector page, where you can browse and choose
|
||||||
|
your favorite movie poster based on your Letterboxd diary.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|||||||
BIN
src/static/images/pulp.gif
Normal file
BIN
src/static/images/pulp.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
Reference in New Issue
Block a user