update style and setup gh-pages
This commit is contained in:
1
.env
Normal file
1
.env
Normal file
@@ -0,0 +1 @@
|
||||
REACT_APP_API_URL=https://letterboxd-diary-posters-picker-backend.onrender.com
|
||||
484
package-lock.json
generated
484
package-lock.json
generated
@@ -28,7 +28,11 @@
|
||||
"react-scripts": "5.0.1",
|
||||
"redux": "^5.0.1",
|
||||
"redux-persist": "^6.0.0",
|
||||
"sass": "^1.80.3",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gh-pages": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
@@ -4293,6 +4297,279 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz",
|
||||
"integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"micromatch": "^4.0.5",
|
||||
"node-addon-api": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher-android-arm64": "2.4.1",
|
||||
"@parcel/watcher-darwin-arm64": "2.4.1",
|
||||
"@parcel/watcher-darwin-x64": "2.4.1",
|
||||
"@parcel/watcher-freebsd-x64": "2.4.1",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.4.1",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.4.1",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.4.1",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.4.1",
|
||||
"@parcel/watcher-linux-x64-musl": "2.4.1",
|
||||
"@parcel/watcher-win32-arm64": "2.4.1",
|
||||
"@parcel/watcher-win32-ia32": "2.4.1",
|
||||
"@parcel/watcher-win32-x64": "2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz",
|
||||
"integrity": "sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz",
|
||||
"integrity": "sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz",
|
||||
"integrity": "sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz",
|
||||
"integrity": "sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz",
|
||||
"integrity": "sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz",
|
||||
"integrity": "sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz",
|
||||
"integrity": "sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz",
|
||||
"integrity": "sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz",
|
||||
"integrity": "sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz",
|
||||
"integrity": "sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz",
|
||||
"integrity": "sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz",
|
||||
"integrity": "sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
@@ -8271,6 +8548,18 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"detect-libc": "bin/detect-libc.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-newline": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
||||
@@ -8541,6 +8830,13 @@
|
||||
"integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/email-addresses": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz",
|
||||
"integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emittery": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz",
|
||||
@@ -9879,6 +10175,34 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/filename-reserved-regex": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
|
||||
"integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/filenamify": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz",
|
||||
"integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"filename-reserved-regex": "^2.0.0",
|
||||
"strip-outer": "^1.0.1",
|
||||
"trim-repeated": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/filesize": {
|
||||
"version": "8.0.7",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz",
|
||||
@@ -10427,6 +10751,54 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gh-pages": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.2.0.tgz",
|
||||
"integrity": "sha512-HMXJ8th9u5wRXaZCnLcs/d3oVvCHiZkaP5KQExQljYGwJjQbSPyTdHe/Gc1IvYUR/rWiZLxNobIqfoMHKTKjHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"async": "^3.2.4",
|
||||
"commander": "^11.0.0",
|
||||
"email-addresses": "^5.0.0",
|
||||
"filenamify": "^4.3.0",
|
||||
"find-cache-dir": "^3.3.1",
|
||||
"fs-extra": "^11.1.1",
|
||||
"globby": "^11.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"gh-pages": "bin/gh-pages.js",
|
||||
"gh-pages-clean": "bin/gh-pages-clean.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/gh-pages/node_modules/commander": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
||||
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/gh-pages/node_modules/fs-extra": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
||||
"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -11037,6 +11409,12 @@
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
|
||||
"integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
@@ -14748,6 +15126,12 @@
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
@@ -15268,9 +15652,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
|
||||
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
@@ -15389,9 +15773,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.41",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
|
||||
"integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
|
||||
"version": "8.4.47",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -15409,8 +15793,8 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.1",
|
||||
"source-map-js": "^1.2.0"
|
||||
"picocolors": "^1.1.0",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
@@ -17914,6 +18298,24 @@
|
||||
"integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.80.3",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.80.3.tgz",
|
||||
"integrity": "sha512-ptDWyVmDMVielpz/oWy3YP3nfs7LpJTHIJZboMVs8GEC9eUmtZTZhMHlTW98wY4aEorDfjN38+Wr/XjskFWcfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@parcel/watcher": "^2.4.1",
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-loader": {
|
||||
"version": "12.6.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz",
|
||||
@@ -17952,6 +18354,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/sass/node_modules/chokidar": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
|
||||
"integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/sass/node_modules/readdirp": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz",
|
||||
"integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
@@ -18344,9 +18774,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -18856,6 +19286,19 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-outer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
|
||||
"integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/style-loader": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz",
|
||||
@@ -19127,9 +19570,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.10",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz",
|
||||
"integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==",
|
||||
"version": "3.4.14",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz",
|
||||
"integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
@@ -19434,6 +19877,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/trim-repeated": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
|
||||
"integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tryer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"homepage": "https://hugyouu.github.io/Letterboxd-Diary-Posters-Picker",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@emotion/styled": "^11.13.0",
|
||||
@@ -23,13 +24,16 @@
|
||||
"react-scripts": "5.0.1",
|
||||
"redux": "^5.0.1",
|
||||
"redux-persist": "^6.0.0",
|
||||
"sass": "^1.80.3",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"eject": "react-scripts eject",
|
||||
"predeploy": "npm run build",
|
||||
"deploy": "gh-pages -d build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -48,5 +52,8 @@
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"gh-pages": "^6.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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