update style and setup gh-pages
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import "./styles/App.css";
|
||||
import "./styles/App.scss";
|
||||
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import { PersistGate } from "redux-persist/integration/react";
|
||||
|
||||
@@ -1,239 +1,282 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import React, { useState } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import {
|
||||
Container, Typography, Card, CardMedia, CardContent,
|
||||
Button, Checkbox, FormControlLabel, TextField, Dialog, DialogTitle, DialogContent, DialogActions,
|
||||
Paper
|
||||
} from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
import GetAppIcon from '@material-ui/icons/GetApp';
|
||||
import ShareIcon from '@material-ui/icons/Share';
|
||||
import EditIcon from '@material-ui/icons/Edit';
|
||||
import { removePoster } from '../services/action';
|
||||
Container,
|
||||
Typography,
|
||||
Card,
|
||||
CardMedia,
|
||||
CardContent,
|
||||
Button,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
TextField,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Paper,
|
||||
} from "@material-ui/core";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import GetAppIcon from "@material-ui/icons/GetApp";
|
||||
import ShareIcon from "@material-ui/icons/Share";
|
||||
import EditIcon from "@material-ui/icons/Edit";
|
||||
import { removePoster } from "../services/action";
|
||||
import axios from "axios";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
marginTop: theme.spacing(4),
|
||||
marginBottom: theme.spacing(8),
|
||||
display: 'flex',
|
||||
},
|
||||
posterList: {
|
||||
flex: 2,
|
||||
marginRight: theme.spacing(2),
|
||||
maxHeight: 'calc(100vh - 200px)',
|
||||
overflowY: 'auto',
|
||||
},
|
||||
actionPanel: {
|
||||
flex: 1,
|
||||
position: 'sticky',
|
||||
top: theme.spacing(4),
|
||||
height: 'fit-content',
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
marginBottom: theme.spacing(2),
|
||||
backgroundColor: '#2c3e50',
|
||||
},
|
||||
cardMedia: {
|
||||
width: 100,
|
||||
},
|
||||
cardContent: {
|
||||
flex: '1 0 auto',
|
||||
},
|
||||
actions: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(2),
|
||||
},
|
||||
slider: {
|
||||
width: '100%',
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
button: {
|
||||
width: '100%',
|
||||
},
|
||||
typography: {
|
||||
color: '#ecf0f1',
|
||||
},
|
||||
formControl: {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
checkbox: {
|
||||
color: '#3498db',
|
||||
},
|
||||
paper: {
|
||||
padding: theme.spacing(3),
|
||||
backgroundColor: '#34495e',
|
||||
},
|
||||
root: {
|
||||
marginTop: theme.spacing(4),
|
||||
marginBottom: theme.spacing(8),
|
||||
display: "flex",
|
||||
},
|
||||
posterList: {
|
||||
flex: 2,
|
||||
marginRight: theme.spacing(2),
|
||||
maxHeight: "calc(100vh - 200px)",
|
||||
overflowY: "auto",
|
||||
},
|
||||
actionPanel: {
|
||||
flex: 1,
|
||||
position: "sticky",
|
||||
top: theme.spacing(4),
|
||||
height: "fit-content",
|
||||
},
|
||||
card: {
|
||||
display: "flex",
|
||||
marginBottom: theme.spacing(2),
|
||||
backgroundColor: "#2c3e50",
|
||||
},
|
||||
cardMedia: {
|
||||
width: 100,
|
||||
},
|
||||
cardContent: {
|
||||
flex: "1 0 auto",
|
||||
},
|
||||
actions: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(2),
|
||||
},
|
||||
slider: {
|
||||
width: "100%",
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
button: {
|
||||
width: "100%",
|
||||
},
|
||||
typography: {
|
||||
color: "#ecf0f1",
|
||||
},
|
||||
formControl: {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
checkbox: {
|
||||
color: "#3498db",
|
||||
},
|
||||
paper: {
|
||||
padding: theme.spacing(3),
|
||||
backgroundColor: "#34495e",
|
||||
},
|
||||
}));
|
||||
|
||||
const Cart = () => {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const selectedPosters = useSelector((state) => {
|
||||
const selections = state.posterSelections;
|
||||
return Object.entries(selections).flatMap(([movieId, posters]) =>
|
||||
posters.map(posterId => ({ movieId, posterId }))
|
||||
);
|
||||
});
|
||||
|
||||
const [selectedForDownload, setSelectedForDownload] = useState(selectedPosters.map(() => true));
|
||||
const [downloadFormat, setDownloadFormat] = useState('zip');
|
||||
const [renameDialogOpen, setRenameDialogOpen] = useState(false);
|
||||
const [currentPosterIndex, setCurrentPosterIndex] = useState(null);
|
||||
const [newPosterName, setNewPosterName] = useState('');
|
||||
|
||||
const handleRemovePoster = (index) => {
|
||||
dispatch(removePoster(selectedPosters[index].movieId, selectedPosters[index].posterId));
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
const postersToDownload = selectedPosters
|
||||
.filter((_, index) => selectedForDownload[index])
|
||||
.map((poster) => ({
|
||||
movieId: poster.movieId,
|
||||
posterId: poster.posterId,
|
||||
movieName: poster.movieName,
|
||||
movieYear: poster.movieYear
|
||||
}));
|
||||
|
||||
console.log('Downloading posters:', postersToDownload);
|
||||
|
||||
try {
|
||||
const response = await axios.post('http://localhost:5000/api/download-posters', {
|
||||
posters: postersToDownload,
|
||||
format: downloadFormat,
|
||||
}, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
|
||||
const blob = new Blob([response.data], { type: response.headers['content-type'] });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = `posters.${downloadFormat}`;
|
||||
link.click();
|
||||
} catch (error) {
|
||||
console.error('Failed to download posters:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleShare = () => {
|
||||
console.log('Sharing selected posters');
|
||||
};
|
||||
|
||||
const handleRename = (index) => {
|
||||
setCurrentPosterIndex(index);
|
||||
setNewPosterName(selectedPosters[index].posterId);
|
||||
setRenameDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleRenameConfirm = () => {
|
||||
console.log(`Renaming poster ${currentPosterIndex} to ${newPosterName}`);
|
||||
setRenameDialogOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container className={classes.root}>
|
||||
<div className={classes.posterList}>
|
||||
<Typography variant="h4" gutterBottom className={classes.typography}>Your Cart</Typography>
|
||||
{selectedPosters.map((poster, index) => (
|
||||
<Card key={index} className={classes.card}>
|
||||
<CardMedia
|
||||
className={classes.cardMedia}
|
||||
image={`https://image.tmdb.org/t/p/w500${poster.posterId}`}
|
||||
title={`Poster ${index + 1}`}
|
||||
/>
|
||||
<CardContent className={classes.cardContent}>
|
||||
<Typography variant="h6"
|
||||
className={classes.typography}>{`Movie ID: ${poster.movieId}`}</Typography>
|
||||
<Typography variant="body2"
|
||||
className={classes.typography}>{poster.posterId}</Typography>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={selectedForDownload[index]}
|
||||
onChange={(e) => {
|
||||
const newSelected = [...selectedForDownload];
|
||||
newSelected[index] = e.target.checked;
|
||||
setSelectedForDownload(newSelected);
|
||||
}}
|
||||
className={classes.checkbox}
|
||||
/>
|
||||
}
|
||||
label="Select for download"
|
||||
className={classes.typography}
|
||||
/>
|
||||
<Button startIcon={<DeleteIcon/>} onClick={() => handleRemovePoster(index)}
|
||||
className={classes.typography}>Remove</Button>
|
||||
<Button startIcon={<EditIcon/>} onClick={() => handleRename(index)}
|
||||
className={classes.typography}>Rename</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<Paper className={classes.actionPanel} elevation={3}>
|
||||
<div className={classes.actions}>
|
||||
<Typography gutterBottom>Download Format</Typography>
|
||||
<TextField
|
||||
select
|
||||
value={downloadFormat}
|
||||
onChange={(e) => setDownloadFormat(e.target.value)}
|
||||
SelectProps={{
|
||||
native: true,
|
||||
}}
|
||||
className={classes.formControl}
|
||||
>
|
||||
<option value="zip">ZIP</option>
|
||||
<option value="tar">TAR</option>
|
||||
<option value="7z">7Z</option>
|
||||
</TextField>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<GetAppIcon/>}
|
||||
onClick={handleDownload}
|
||||
disabled={selectedForDownload.every((selected) => !selected)}
|
||||
className={classes.button}
|
||||
>
|
||||
Download Selected
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
startIcon={<ShareIcon/>}
|
||||
onClick={handleShare}
|
||||
className={classes.button}
|
||||
>
|
||||
Share Selection
|
||||
</Button>
|
||||
</div>
|
||||
</Paper>
|
||||
<Dialog open={renameDialogOpen} onClose={() => setRenameDialogOpen(false)}>
|
||||
<DialogTitle>Rename Poster</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
label="New Name"
|
||||
type="text"
|
||||
fullWidth
|
||||
value={newPosterName}
|
||||
onChange={(e) => setNewPosterName(e.target.value)}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setRenameDialogOpen(false)} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleRenameConfirm} color="primary">
|
||||
Rename
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Container>
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const selectedPosters = useSelector((state) => {
|
||||
const selections = state.posterSelections;
|
||||
return Object.entries(selections).flatMap(([movieId, posters]) =>
|
||||
posters.map((posterId) => ({ movieId, posterId }))
|
||||
);
|
||||
});
|
||||
|
||||
const [selectedForDownload, setSelectedForDownload] = useState(
|
||||
selectedPosters.map(() => true)
|
||||
);
|
||||
const [downloadFormat, setDownloadFormat] = useState("zip");
|
||||
const [renameDialogOpen, setRenameDialogOpen] = useState(false);
|
||||
const [currentPosterIndex, setCurrentPosterIndex] = useState(null);
|
||||
const [newPosterName, setNewPosterName] = useState("");
|
||||
|
||||
const handleRemovePoster = (index) => {
|
||||
dispatch(
|
||||
removePoster(
|
||||
selectedPosters[index].movieId,
|
||||
selectedPosters[index].posterId
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
const postersToDownload = selectedPosters
|
||||
.filter((_, index) => selectedForDownload[index])
|
||||
.map((poster) => ({
|
||||
movieId: poster.movieId,
|
||||
posterId: poster.posterId,
|
||||
movieName: poster.movieName,
|
||||
movieYear: poster.movieYear,
|
||||
}));
|
||||
|
||||
console.log("Downloading posters:", postersToDownload);
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
"http://localhost:5000/api/download-posters",
|
||||
{
|
||||
posters: postersToDownload,
|
||||
format: downloadFormat,
|
||||
},
|
||||
{
|
||||
responseType: "blob",
|
||||
}
|
||||
);
|
||||
|
||||
const blob = new Blob([response.data], {
|
||||
type: response.headers["content-type"],
|
||||
});
|
||||
const link = document.createElement("a");
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = `posters.${downloadFormat}`;
|
||||
link.click();
|
||||
} catch (error) {
|
||||
console.error("Failed to download posters:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleShare = () => {
|
||||
console.log("Sharing selected posters");
|
||||
};
|
||||
|
||||
const handleRename = (index) => {
|
||||
setCurrentPosterIndex(index);
|
||||
setNewPosterName(selectedPosters[index].posterId);
|
||||
setRenameDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleRenameConfirm = () => {
|
||||
console.log(`Renaming poster ${currentPosterIndex} to ${newPosterName}`);
|
||||
setRenameDialogOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container className="cart-container">
|
||||
<div className="poster-list">
|
||||
<Typography variant="h4" gutterBottom className="cart-title">
|
||||
Your Cart
|
||||
</Typography>
|
||||
{selectedPosters.map((poster, index) => (
|
||||
<Card key={index} className="poster-card">
|
||||
<CardMedia
|
||||
className="poster-media"
|
||||
image={`https://image.tmdb.org/t/p/w500${poster.posterId}`}
|
||||
title={`Poster ${index + 1}`}
|
||||
/>
|
||||
<CardContent className="poster-content">
|
||||
<Typography variant="h6" className="poster-title">
|
||||
{`Movie ID: ${poster.movieId}`}
|
||||
</Typography>
|
||||
<Typography variant="body2" className="poster-id">
|
||||
{poster.posterId}
|
||||
</Typography>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={selectedForDownload[index]}
|
||||
onChange={(e) => {
|
||||
const newSelected = [...selectedForDownload];
|
||||
newSelected[index] = e.target.checked;
|
||||
setSelectedForDownload(newSelected);
|
||||
}}
|
||||
className="checkbox"
|
||||
/>
|
||||
}
|
||||
label="Select for download"
|
||||
/>
|
||||
<Button
|
||||
startIcon={<DeleteIcon />}
|
||||
onClick={() => handleRemovePoster(index)}
|
||||
className="action-button"
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<EditIcon />}
|
||||
onClick={() => handleRename(index)}
|
||||
className="action-button"
|
||||
>
|
||||
Rename
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<Paper className="action-panel" elevation={3}>
|
||||
<div className="action-buttons">
|
||||
<Typography gutterBottom className="action-title">
|
||||
Download Format
|
||||
</Typography>
|
||||
<TextField
|
||||
select
|
||||
value={downloadFormat}
|
||||
onChange={(e) => setDownloadFormat(e.target.value)}
|
||||
SelectProps={{
|
||||
native: true,
|
||||
}}
|
||||
className="form-control"
|
||||
>
|
||||
<option value="zip">ZIP</option>
|
||||
<option value="tar">TAR</option>
|
||||
<option value="7z">7Z</option>
|
||||
</TextField>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<GetAppIcon />}
|
||||
onClick={handleDownload}
|
||||
disabled={selectedForDownload.every((selected) => !selected)}
|
||||
className="primary-button"
|
||||
>
|
||||
Download Selected
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
startIcon={<ShareIcon />}
|
||||
onClick={handleShare}
|
||||
className="secondary-button"
|
||||
>
|
||||
Share Selection
|
||||
</Button>
|
||||
</div>
|
||||
</Paper>
|
||||
<Dialog
|
||||
open={renameDialogOpen}
|
||||
onClose={() => setRenameDialogOpen(false)}
|
||||
className="rename-dialog"
|
||||
>
|
||||
<DialogTitle className="dialog-title">Rename Poster</DialogTitle>
|
||||
<DialogContent className="dialog-content">
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
label="New Name"
|
||||
type="text"
|
||||
fullWidth
|
||||
value={newPosterName}
|
||||
onChange={(e) => setNewPosterName(e.target.value)}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions className="dialog-actions">
|
||||
<Button onClick={() => setRenameDialogOpen(false)} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleRenameConfirm} color="primary">
|
||||
Rename
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Cart;
|
||||
export default Cart;
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
import CheckIcon from "@material-ui/icons/Check";
|
||||
import NavBar from "./NavBar";
|
||||
|
||||
const apiUrl = process.env.REACT_APP_API_URL;
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
marginTop: theme.spacing(4),
|
||||
@@ -85,14 +87,12 @@ const PosterGallery = ({ movieId }) => {
|
||||
(state) => state.posterSelections[movieId] || []
|
||||
);
|
||||
|
||||
console.log(movieId);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPosters = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(
|
||||
`http://localhost:5000/api/posters/${movieName}/${movieYear}`
|
||||
`${apiUrl}/api/posters/${movieName}/${movieYear}`
|
||||
);
|
||||
setPosters(response.data.posters);
|
||||
} catch (error) {
|
||||
@@ -115,39 +115,39 @@ const PosterGallery = ({ movieId }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container className={classes.root}>
|
||||
<Paper elevation={3} className={classes.paper}>
|
||||
<Typography variant="h4" gutterBottom className={classes.title}>
|
||||
<Container className="poster-gallery">
|
||||
<Paper elevation={3} className="content-paper">
|
||||
<Typography variant="h4" gutterBottom className="title">
|
||||
Posters for {movieName} ({movieYear})
|
||||
</Typography>
|
||||
{loading ? (
|
||||
<Box className={classes.loadingContainer}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
<Box className="loading-container">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : (
|
||||
<Grid container spacing={3}>
|
||||
{posters.map((poster, index) => (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} key={index}>
|
||||
<Card
|
||||
className={`${classes.posterCard} ${
|
||||
selectedPosters.includes(poster.file_path)
|
||||
? classes.selectedPoster
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => handlePosterSelect(poster.file_path)}
|
||||
>
|
||||
<CardMedia
|
||||
className={classes.posterImage}
|
||||
image={`https://image.tmdb.org/t/p/w500${poster.file_path}`}
|
||||
title={`${movieName} poster ${index + 1}`}
|
||||
/>
|
||||
{selectedPosters.includes(poster.file_path) && (
|
||||
<CheckIcon className={classes.checkIcon} />
|
||||
)}
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
<Grid container spacing={3}>
|
||||
{posters.map((poster, index) => (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} key={index}>
|
||||
<Card
|
||||
className={`poster-card ${
|
||||
selectedPosters.includes(poster.file_path)
|
||||
? "selected-poster"
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => handlePosterSelect(poster.file_path)}
|
||||
>
|
||||
<CardMedia
|
||||
className="poster-image"
|
||||
image={`https://image.tmdb.org/t/p/w500${poster.file_path}`}
|
||||
title={`${movieName} poster ${index + 1}`}
|
||||
/>
|
||||
{selectedPosters.includes(poster.file_path) && (
|
||||
<CheckIcon className="check-icon" />
|
||||
)}
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</Paper>
|
||||
</Container>
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
LinearProgress,
|
||||
Box,
|
||||
Paper,
|
||||
makeStyles,
|
||||
Button,
|
||||
} from "@material-ui/core";
|
||||
import Pagination from "@mui/material/Pagination";
|
||||
@@ -19,127 +18,7 @@ import queryString from "query-string";
|
||||
import pulpGif from "../static/images/pulp.gif";
|
||||
import NavBar from "./NavBar";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
marginTop: theme.spacing(4),
|
||||
marginBottom: theme.spacing(8),
|
||||
},
|
||||
paper: {
|
||||
padding: theme.spacing(3),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: "#1c1f23",
|
||||
color: "white",
|
||||
},
|
||||
title: {
|
||||
marginBottom: theme.spacing(3),
|
||||
},
|
||||
username: {
|
||||
color: "#00A346",
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
list: {
|
||||
maxHeight: "800px",
|
||||
minHeight: "900px",
|
||||
},
|
||||
listItem: {
|
||||
marginBottom: theme.spacing(2),
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
},
|
||||
"&:not(:last-child)": {
|
||||
borderBottom: `1px solid #667788`,
|
||||
paddingTop: theme.spacing(0.5),
|
||||
paddingBottom: theme.spacing(0.5),
|
||||
},
|
||||
},
|
||||
poster: {
|
||||
width: "50px",
|
||||
height: "75px",
|
||||
objectFit: "cover",
|
||||
marginRight: theme.spacing(2),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
},
|
||||
movieInfo: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
movieTitle: {
|
||||
color: "white",
|
||||
fontFamily: "TiemposTextWeb-Semibold, Georgia, serif",
|
||||
fontSize: "1.38461538rem",
|
||||
fontWeight: "400",
|
||||
|
||||
"&:hover": {
|
||||
color: "var(--primary)",
|
||||
},
|
||||
},
|
||||
movieYear: {
|
||||
color: "#667788",
|
||||
},
|
||||
watchedDate: {
|
||||
color: "#667788",
|
||||
textAlign: "center",
|
||||
},
|
||||
watchedDay: {
|
||||
fontSize: "2rem",
|
||||
},
|
||||
refreshButton: {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
paginationContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
progressContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
height: "200px",
|
||||
},
|
||||
progressLabel: {
|
||||
marginTop: theme.spacing(2),
|
||||
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 apiUrl = process.env.REACT_APP_API_URL;
|
||||
|
||||
const paginationTheme = createTheme({
|
||||
palette: {
|
||||
@@ -173,7 +52,6 @@ const paginationTheme = createTheme({
|
||||
});
|
||||
|
||||
const PosterSelector = () => {
|
||||
const classes = useStyles();
|
||||
const [movies, setMovies] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
@@ -182,20 +60,19 @@ const PosterSelector = () => {
|
||||
const [username, setUsername] = useState("");
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const moviesPerPage = 10;
|
||||
const moviesPerPage = 8;
|
||||
|
||||
useEffect(() => {
|
||||
const parsed = queryString.parse(location.search);
|
||||
const pageNumber = parsed.page ? parseInt(parsed.page, 10) : 1;
|
||||
setPage(pageNumber);
|
||||
|
||||
fetchMovies(pageNumber).then(r => console.log(r));
|
||||
// fetchUsername();
|
||||
fetchMovies(pageNumber).then((r) => console.log(r));
|
||||
}, [location.search]);
|
||||
|
||||
const fetchUsername = async () => {
|
||||
try {
|
||||
const response = await axios.get("http://localhost:5000/api/username");
|
||||
const response = await axios.get(`${apiUrl}/api/username`);
|
||||
setUsername(response.data.username);
|
||||
} catch (error) {
|
||||
console.error("Error fetching username:", error);
|
||||
@@ -208,7 +85,7 @@ const PosterSelector = () => {
|
||||
setProgress(0);
|
||||
|
||||
const response = await axios.get(
|
||||
`http://localhost:5000/api/movies?page=${pageNumber}&limit=${moviesPerPage}`,
|
||||
`${apiUrl}/api/movies?page=${pageNumber}&limit=${moviesPerPage}`,
|
||||
{
|
||||
onDownloadProgress: (progressEvent) => {
|
||||
const percentCompleted = Math.round(
|
||||
@@ -240,7 +117,7 @@ const PosterSelector = () => {
|
||||
|
||||
const handleRefreshFiles = async () => {
|
||||
try {
|
||||
await axios.delete("http://localhost:5000/api/delete-csv");
|
||||
await axios.delete(`${apiUrl}/api/delete-csv`);
|
||||
navigate("/");
|
||||
} catch (error) {
|
||||
console.error("Error deleting CSV file:", error);
|
||||
@@ -267,121 +144,116 @@ const PosterSelector = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container className={classes.root}>
|
||||
<Paper elevation={3} className={classes.paper}>
|
||||
<Typography variant="h4" gutterBottom className={classes.title}>
|
||||
<Container className="poster-selector">
|
||||
<Paper elevation={3} className="content-paper">
|
||||
<Typography variant="h4" className="title">
|
||||
Your diary
|
||||
</Typography>
|
||||
|
||||
{username && (
|
||||
<Typography variant="h6" className={classes.username}>
|
||||
Letterboxd User: {username}
|
||||
</Typography>
|
||||
<Typography variant="h6" className="username">
|
||||
Letterboxd User: {username}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<Box className={classes.progressContainer}>
|
||||
<LinearProgress variant="determinate" value={progress} />
|
||||
<Typography className={classes.progressLabel}>
|
||||
Downloading movies...
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box className="progress-container">
|
||||
<LinearProgress />
|
||||
<Typography className="progress-label">
|
||||
Downloading movies...
|
||||
</Typography>
|
||||
</Box>
|
||||
) : movies.length > 0 ? (
|
||||
<>
|
||||
<List className={classes.list}>
|
||||
{movies.map((movie, index) => (
|
||||
<ListItem
|
||||
button
|
||||
key={index}
|
||||
onClick={() => handleMovieClick(movie.Name, movie.Year)}
|
||||
className={classes.listItem}
|
||||
>
|
||||
<Grid container alignItems="center">
|
||||
<Grid item>
|
||||
<img
|
||||
src={
|
||||
`https://image.tmdb.org/t/p/w500${movie.Poster.file_path}` ||
|
||||
`/api/placeholder/50/75`
|
||||
}
|
||||
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>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
<Box className={classes.paginationContainer}>
|
||||
<ThemeProvider theme={paginationTheme}>
|
||||
<Pagination
|
||||
count={totalPages}
|
||||
page={page}
|
||||
onChange={handlePageChange}
|
||||
color="primary"
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<Box className={classes.noMoviesContainer}>
|
||||
<div className={classes.gifContainer}>
|
||||
<div className={classes.circleBackground}></div>
|
||||
<img
|
||||
src={pulpGif}
|
||||
alt="Confused reaction"
|
||||
className={classes.gif}
|
||||
<>
|
||||
<List className="movies-list">
|
||||
{movies.map((movie, index) => (
|
||||
<ListItem
|
||||
button
|
||||
key={index}
|
||||
onClick={() => handleMovieClick(movie.Name, movie.Year)}
|
||||
className="movie-item"
|
||||
>
|
||||
<Grid container alignItems="center">
|
||||
<Grid item>
|
||||
<img
|
||||
src={
|
||||
`https://image.tmdb.org/t/p/w500${movie.Poster.file_path}` ||
|
||||
`/api/placeholder/50/75`
|
||||
}
|
||||
alt={movie.Name}
|
||||
className="movie-poster"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item className="movie-info">
|
||||
<Typography variant="subtitle1" className="movie-title">
|
||||
{movie.Name}
|
||||
</Typography>
|
||||
<Typography variant="body2" className="movie-year">
|
||||
{movie.Year}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Box className="watched-date">
|
||||
{(() => {
|
||||
const { day, month } = formatWatchedDate(
|
||||
movie["Watched Date"]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="watched-day"
|
||||
>
|
||||
{day}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className="watched-month"
|
||||
>
|
||||
{month}
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
<Box className="pagination-container">
|
||||
<ThemeProvider theme={paginationTheme}>
|
||||
<Pagination
|
||||
count={totalPages}
|
||||
page={page}
|
||||
onChange={handlePageChange}
|
||||
color="primary"
|
||||
/>
|
||||
</div>
|
||||
<Typography variant="body1" gutterBottom>
|
||||
No movies found in your diary. Please check your Letterboxd
|
||||
username or CSV file.
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
className={classes.backButton}
|
||||
onClick={handleGoBack}
|
||||
>
|
||||
Go Back
|
||||
</Button>
|
||||
</ThemeProvider>
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<Box className="no-movies-container">
|
||||
<div className="gif-container">
|
||||
<div className="circle-background"></div>
|
||||
<img
|
||||
src={pulpGif}
|
||||
alt="Confused reaction"
|
||||
className="reaction-gif"
|
||||
/>
|
||||
</div>
|
||||
<Typography variant="body1" gutterBottom>
|
||||
No movies found in your diary. Please check your Letterboxd
|
||||
username or CSV file.
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
className="back-button"
|
||||
onClick={handleGoBack}
|
||||
>
|
||||
Go Back
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
</Container>
|
||||
|
||||
@@ -7,103 +7,15 @@ import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
TextField,
|
||||
makeStyles,
|
||||
} from "@material-ui/core";
|
||||
import { CloudUploadOutlined } from "@material-ui/icons";
|
||||
import CloudDoneOutlinedIcon from "@mui/icons-material/CloudDoneOutlined";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import axios from "axios";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
padding: theme.spacing(4),
|
||||
backgroundColor: "#14181c",
|
||||
minHeight: "100vh",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
card: {
|
||||
width: "100%",
|
||||
maxWidth: "600px",
|
||||
backgroundColor: "#1c1f23",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
boxShadow: theme.shadows[3],
|
||||
padding: theme.spacing(4),
|
||||
},
|
||||
title: {
|
||||
marginBottom: theme.spacing(3),
|
||||
fontWeight: "bold",
|
||||
color: "#ffffff",
|
||||
},
|
||||
dropzone: {
|
||||
border: `2px dashed #00A346`,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
padding: theme.spacing(4),
|
||||
textAlign: "center",
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
backgroundColor: "#1f252a",
|
||||
},
|
||||
},
|
||||
dropzoneIcon: {
|
||||
marginBottom: theme.spacing(2),
|
||||
color: "#00A346",
|
||||
},
|
||||
dropzoneText: {
|
||||
marginBottom: theme.spacing(2),
|
||||
color: "#ffffff",
|
||||
},
|
||||
progressContainer: {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
submitButton: {
|
||||
color: "#fff",
|
||||
fontSize: "0.8rem",
|
||||
fontWeight: "900",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: "0.04em",
|
||||
lineHeight: "2.8rem",
|
||||
display: "inline-block",
|
||||
cursor: "pointer",
|
||||
padding: "0 1rem",
|
||||
border: "0",
|
||||
borderRadius: "4px",
|
||||
outline: "none",
|
||||
background: "#526e89",
|
||||
transition: "background-color 0.3s ease",
|
||||
"&:hover": {
|
||||
backgroundColor: "#1caff2",
|
||||
},
|
||||
},
|
||||
overviewText: {
|
||||
color: "#456",
|
||||
marginTop: theme.spacing(4),
|
||||
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 apiUrl = process.env.REACT_APP_API_URL;
|
||||
|
||||
const UploadDiary = () => {
|
||||
const classes = useStyles();
|
||||
const [file, setFile] = useState(null);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [username, setUsername] = useState("");
|
||||
@@ -112,7 +24,7 @@ const UploadDiary = () => {
|
||||
useEffect(() => {
|
||||
const checkCSVFile = async () => {
|
||||
try {
|
||||
const response = await axios.get("http://localhost:5000/api/check-csv");
|
||||
const response = await axios.get(`${apiUrl}/api/check-csv`);
|
||||
if (response.data.fileExists) {
|
||||
navigate("/PosterSelector");
|
||||
}
|
||||
@@ -136,16 +48,14 @@ const UploadDiary = () => {
|
||||
setUploading(true);
|
||||
try {
|
||||
if (username) {
|
||||
// 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
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
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(`${apiUrl}/api/upload-csv`, formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
@@ -162,13 +72,13 @@ const UploadDiary = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Container className={classes.root}>
|
||||
<div className={classes.card}>
|
||||
<Typography variant="h4" className={classes.title}>
|
||||
<Container className="upload-diary">
|
||||
<div className="upload-card">
|
||||
<Typography variant="h4" className="title">
|
||||
Upload Letterboxd Diary
|
||||
</Typography>
|
||||
<TextField
|
||||
className={classes.usernameField}
|
||||
className="username-field"
|
||||
label="Letterboxd Username"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
@@ -176,42 +86,33 @@ const UploadDiary = () => {
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="Enter your Letterboxd username"
|
||||
/>
|
||||
<Typography
|
||||
variant="body2"
|
||||
style={{ color: "#ffffff", marginBottom: "1rem" }}
|
||||
>
|
||||
<Typography variant="body2" className="or-text">
|
||||
Or upload a CSV file:
|
||||
</Typography>
|
||||
<div {...getRootProps()} className={classes.dropzone}>
|
||||
<div {...getRootProps()} className="dropzone">
|
||||
<input {...getInputProps()} />
|
||||
{file ? (
|
||||
<>
|
||||
<CloudDoneOutlinedIcon
|
||||
className={classes.dropzoneIcon}
|
||||
style={{ fontSize: "4rem" }}
|
||||
/>
|
||||
<Typography variant="h6" className={classes.dropzoneText}>
|
||||
<CloudDoneOutlinedIcon className="dropzone-icon" />
|
||||
<Typography variant="h6" className="dropzone-text">
|
||||
Your file has been uploaded: {file.name}
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CloudUploadOutlined
|
||||
className={classes.dropzoneIcon}
|
||||
style={{ fontSize: "4rem" }}
|
||||
/>
|
||||
<Typography variant="h6" className={classes.dropzoneText}>
|
||||
<CloudUploadOutlined className="dropzone-icon" />
|
||||
<Typography variant="h6" className="dropzone-text">
|
||||
Drag and drop a CSV file here or click to select
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Grid container justify="center" className={classes.progressContainer}>
|
||||
<Grid container justify="center" className="progress-container">
|
||||
{uploading ? (
|
||||
<CircularProgress />
|
||||
) : (
|
||||
<Button
|
||||
className={classes.submitButton}
|
||||
className="submit-button"
|
||||
variant="contained"
|
||||
onClick={handleUpload}
|
||||
disabled={!username && !file}
|
||||
@@ -221,7 +122,7 @@ const UploadDiary = () => {
|
||||
)}
|
||||
</Grid>
|
||||
</div>
|
||||
<Typography variant="body1" className={classes.overviewText}>
|
||||
<Typography variant="body1" className="overview-text">
|
||||
This tool allows you to easily import your Letterboxd diary and select
|
||||
your favorite movie poster. Follow these simple steps:
|
||||
<br />
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
@use "./scss/uploadDiary";
|
||||
@use "./scss/posterSelector";
|
||||
@use "./scss/posterGallery";
|
||||
@use "./scss/cart";
|
||||
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ code {
|
||||
|
||||
:root {
|
||||
--background: #fff;
|
||||
--bgComp: #1c1f23;
|
||||
--foreground: #2c3e50;
|
||||
--primary: #1caff2;
|
||||
--secondary: #000;
|
||||
|
||||
166
src/styles/scss/_cart.scss
Normal file
166
src/styles/scss/_cart.scss
Normal file
@@ -0,0 +1,166 @@
|
||||
// Variables
|
||||
$primary-color: #00a346;
|
||||
$secondary-color: #3498db;
|
||||
$background-dark: #2c3e50;
|
||||
$background-light: #34495e;
|
||||
$text-white: #ecf0f1;
|
||||
$text-muted: #95a5a6;
|
||||
$hover-scale: 1.05;
|
||||
$gap-spacing: 1rem;
|
||||
|
||||
.cart-container {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 4rem;
|
||||
display: flex;
|
||||
gap: $gap-spacing;
|
||||
|
||||
.poster-list {
|
||||
flex: 2;
|
||||
margin-right: 2rem;
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow-y: auto;
|
||||
|
||||
.cart-title {
|
||||
color: $text-white;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 2rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.poster-card {
|
||||
display: flex;
|
||||
margin-bottom: 1rem;
|
||||
background-color: $background-dark;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
.poster-media {
|
||||
width: 120px;
|
||||
height: 180px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.poster-content {
|
||||
flex-grow: 1;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.poster-title {
|
||||
color: $text-white;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.poster-id {
|
||||
color: $text-muted;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.poster-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
|
||||
.action-button {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
background-color: transparent;
|
||||
border: 1px solid $text-muted;
|
||||
color: $text-white;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: $background-light;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-panel {
|
||||
flex: 1;
|
||||
position: sticky;
|
||||
top: 2rem;
|
||||
height: fit-content;
|
||||
padding: 1.5rem;
|
||||
background-color: $background-light;
|
||||
border-radius: 8px;
|
||||
|
||||
.action-title {
|
||||
color: $text-white;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
margin-bottom: 1.5rem;
|
||||
color: $text-white;
|
||||
|
||||
select {
|
||||
background-color: $background-dark;
|
||||
color: $text-white;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid $text-muted;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
|
||||
.primary-button,
|
||||
.secondary-button {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.primary-button {
|
||||
background-color: $primary-color;
|
||||
color: $text-white;
|
||||
|
||||
&:hover {
|
||||
background-color: darken($primary-color, 10%);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: darken($primary-color, 20%);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary-button {
|
||||
background-color: $secondary-color;
|
||||
color: $text-white;
|
||||
|
||||
&:hover {
|
||||
background-color: darken($secondary-color, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
src/styles/scss/_posterGallery.scss
Normal file
70
src/styles/scss/_posterGallery.scss
Normal file
@@ -0,0 +1,70 @@
|
||||
// Variables
|
||||
$primary-color: #00a346;
|
||||
$background-dark: #1c1f23;
|
||||
$text-white: #ffffff;
|
||||
$hover-scale: 1.05;
|
||||
$loading-height: 50vh;
|
||||
|
||||
.poster-gallery {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.content-paper {
|
||||
padding: 1.5rem;
|
||||
border-radius: 4px;
|
||||
background-color: $background-dark;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: $text-white;
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 2rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.poster-card {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale($hover-scale);
|
||||
}
|
||||
|
||||
&.selected-poster {
|
||||
border: 4px solid $primary-color;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.poster-image {
|
||||
padding-top: 150%;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 4px;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
background-color: $primary-color;
|
||||
color: $text-white;
|
||||
border-radius: 50%;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: $loading-height;
|
||||
}
|
||||
}
|
||||
192
src/styles/scss/_posterSelector.scss
Normal file
192
src/styles/scss/_posterSelector.scss
Normal file
@@ -0,0 +1,192 @@
|
||||
// Variables
|
||||
$primary-color: #00a346;
|
||||
$secondary-color: #667788;
|
||||
$background-dark: #1c1f23;
|
||||
$text-white: #ffffff;
|
||||
$text-gray: #667788;
|
||||
$border-color: #667788;
|
||||
$hover-color: rgba(102, 119, 136, 0.2);
|
||||
|
||||
.poster-selector {
|
||||
padding: 0 2rem;
|
||||
max-width: 100% !important; // Override MUI Container
|
||||
margin: 2rem auto 4rem !important;
|
||||
|
||||
.content-paper {
|
||||
padding: 1.5rem;
|
||||
border-radius: 4px;
|
||||
background-color: $background-dark !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 1.5rem;
|
||||
color: $text-white;
|
||||
}
|
||||
|
||||
.username {
|
||||
color: $primary-color;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.movies-list {
|
||||
max-height: 800px;
|
||||
min-height: 900px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.movie-item {
|
||||
margin-bottom: 1rem;
|
||||
transition: background-color 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
&:hover {
|
||||
background-color: $hover-color;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid $border-color;
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
// Override MUI ListItem styles
|
||||
&.MuiListItem-root {
|
||||
padding: 0.5rem !important;
|
||||
}
|
||||
|
||||
.MuiGrid-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.movie-poster {
|
||||
width: 50px;
|
||||
height: 75px;
|
||||
object-fit: cover;
|
||||
margin-right: 1rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.movie-info {
|
||||
flex-grow: 1;
|
||||
max-width: calc(100% - 110px);
|
||||
}
|
||||
|
||||
.movie-title {
|
||||
color: $text-white;
|
||||
font-family: "TiemposTextWeb-Semibold, Georgia, serif";
|
||||
font-size: 1.38461538rem;
|
||||
font-weight: 400;
|
||||
transition: color 0.3s ease;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.movie-year {
|
||||
color: $text-gray;
|
||||
}
|
||||
|
||||
.watched-date {
|
||||
color: $text-gray;
|
||||
}
|
||||
|
||||
.watched-day {
|
||||
font-size: 2rem;
|
||||
line-height: 1;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.watched-month {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 1.5rem;
|
||||
|
||||
// Override MUI Pagination styles
|
||||
.MuiPagination-root {
|
||||
.MuiPaginationItem-root {
|
||||
color: $text-white;
|
||||
|
||||
&.Mui-selected {
|
||||
background-color: $secondary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
height: 200px;
|
||||
|
||||
.MuiLinearProgress-root {
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
margin-top: 1rem;
|
||||
color: $text-white;
|
||||
}
|
||||
|
||||
.no-movies-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.gif-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.circle-background {
|
||||
position: absolute;
|
||||
width: 120%;
|
||||
height: 120%;
|
||||
border-radius: 50%;
|
||||
background-color: $text-white;
|
||||
clip-path: inset(0 0 25% 0);
|
||||
bottom: -28%;
|
||||
}
|
||||
|
||||
.reaction-gif {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
margin-top: 1rem;
|
||||
background-color: $primary-color !important;
|
||||
color: $text-white !important;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: darken($primary-color, 10%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
112
src/styles/scss/_uploadDiary.scss
Normal file
112
src/styles/scss/_uploadDiary.scss
Normal file
@@ -0,0 +1,112 @@
|
||||
.upload-diary {
|
||||
padding: 3rem;
|
||||
background-color: #14181c;
|
||||
min-height: 100vh;
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.upload-card {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
background-color: #1c1f23;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2);
|
||||
padding: 2rem;
|
||||
|
||||
.title {
|
||||
margin-bottom: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.username-field {
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
.MuiOutlinedInput-root {
|
||||
color: #ffffff;
|
||||
|
||||
fieldset {
|
||||
border-color: #00a346 !important;
|
||||
}
|
||||
|
||||
&:hover fieldset {
|
||||
border-color: #00a346 !important;
|
||||
}
|
||||
|
||||
&.Mui-focused fieldset {
|
||||
border-color: #1caff2 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiInputLabel-root {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
|
||||
.or-text {
|
||||
color: #ffffff;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.dropzone {
|
||||
border: 2px dashed #00a346;
|
||||
border-radius: 4px;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #1f252a;
|
||||
}
|
||||
|
||||
.dropzone-icon {
|
||||
margin-bottom: 1rem;
|
||||
color: #00a346;
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.dropzone-text {
|
||||
margin-bottom: 1rem;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
color: #fff;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 900;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
line-height: 2.8rem;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
padding: 0 1rem;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
background: #526e89;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #1caff2;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overview-text {
|
||||
color: #456;
|
||||
margin-top: 2rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user