update style and setup gh-pages

This commit is contained in:
2024-10-20 00:42:23 +02:00
parent 129fcb672c
commit ba881b1637
14 changed files with 1455 additions and 629 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
REACT_APP_API_URL=https://letterboxd-diary-posters-picker-backend.onrender.com

484
package-lock.json generated
View File

@@ -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",

View File

@@ -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"
}
}

View File

@@ -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";

View File

@@ -1,71 +1,82 @@
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',
display: "flex",
},
posterList: {
flex: 2,
marginRight: theme.spacing(2),
maxHeight: 'calc(100vh - 200px)',
overflowY: 'auto',
maxHeight: "calc(100vh - 200px)",
overflowY: "auto",
},
actionPanel: {
flex: 1,
position: 'sticky',
position: "sticky",
top: theme.spacing(4),
height: 'fit-content',
height: "fit-content",
},
card: {
display: 'flex',
display: "flex",
marginBottom: theme.spacing(2),
backgroundColor: '#2c3e50',
backgroundColor: "#2c3e50",
},
cardMedia: {
width: 100,
},
cardContent: {
flex: '1 0 auto',
flex: "1 0 auto",
},
actions: {
display: 'flex',
flexDirection: 'column',
display: "flex",
flexDirection: "column",
gap: theme.spacing(2),
},
slider: {
width: '100%',
width: "100%",
marginTop: theme.spacing(2),
},
button: {
width: '100%',
width: "100%",
},
typography: {
color: '#ecf0f1',
color: "#ecf0f1",
},
formControl: {
marginBottom: theme.spacing(2),
},
checkbox: {
color: '#3498db',
color: "#3498db",
},
paper: {
padding: theme.spacing(3),
backgroundColor: '#34495e',
backgroundColor: "#34495e",
},
}));
@@ -75,18 +86,25 @@ const Cart = () => {
const selectedPosters = useSelector((state) => {
const selections = state.posterSelections;
return Object.entries(selections).flatMap(([movieId, posters]) =>
posters.map(posterId => ({ movieId, posterId }))
posters.map((posterId) => ({ movieId, posterId }))
);
});
const [selectedForDownload, setSelectedForDownload] = useState(selectedPosters.map(() => true));
const [downloadFormat, setDownloadFormat] = useState('zip');
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 [newPosterName, setNewPosterName] = useState("");
const handleRemovePoster = (index) => {
dispatch(removePoster(selectedPosters[index].movieId, selectedPosters[index].posterId));
dispatch(
removePoster(
selectedPosters[index].movieId,
selectedPosters[index].posterId
)
);
};
const handleDownload = async () => {
@@ -96,31 +114,37 @@ const Cart = () => {
movieId: poster.movieId,
posterId: poster.posterId,
movieName: poster.movieName,
movieYear: poster.movieYear
movieYear: poster.movieYear,
}));
console.log('Downloading posters:', postersToDownload);
console.log("Downloading posters:", postersToDownload);
try {
const response = await axios.post('http://localhost:5000/api/download-posters', {
const response = await axios.post(
"http://localhost:5000/api/download-posters",
{
posters: postersToDownload,
format: downloadFormat,
}, {
responseType: 'blob',
});
},
{
responseType: "blob",
}
);
const blob = new Blob([response.data], { type: response.headers['content-type'] });
const link = document.createElement('a');
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);
console.error("Failed to download posters:", error);
}
};
const handleShare = () => {
console.log('Sharing selected posters');
console.log("Sharing selected posters");
};
const handleRename = (index) => {
@@ -135,21 +159,25 @@ const Cart = () => {
};
return (
<Container className={classes.root}>
<div className={classes.posterList}>
<Typography variant="h4" gutterBottom className={classes.typography}>Your Cart</Typography>
<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={classes.card}>
<Card key={index} className="poster-card">
<CardMedia
className={classes.cardMedia}
className="poster-media"
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>
<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
@@ -159,23 +187,34 @@ const Cart = () => {
newSelected[index] = e.target.checked;
setSelectedForDownload(newSelected);
}}
className={classes.checkbox}
className="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>
<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={classes.actionPanel} elevation={3}>
<div className={classes.actions}>
<Typography gutterBottom>Download Format</Typography>
<Paper className="action-panel" elevation={3}>
<div className="action-buttons">
<Typography gutterBottom className="action-title">
Download Format
</Typography>
<TextField
select
value={downloadFormat}
@@ -183,7 +222,7 @@ const Cart = () => {
SelectProps={{
native: true,
}}
className={classes.formControl}
className="form-control"
>
<option value="zip">ZIP</option>
<option value="tar">TAR</option>
@@ -195,7 +234,7 @@ const Cart = () => {
startIcon={<GetAppIcon />}
onClick={handleDownload}
disabled={selectedForDownload.every((selected) => !selected)}
className={classes.button}
className="primary-button"
>
Download Selected
</Button>
@@ -204,15 +243,19 @@ const Cart = () => {
color="secondary"
startIcon={<ShareIcon />}
onClick={handleShare}
className={classes.button}
className="secondary-button"
>
Share Selection
</Button>
</div>
</Paper>
<Dialog open={renameDialogOpen} onClose={() => setRenameDialogOpen(false)}>
<DialogTitle>Rename Poster</DialogTitle>
<DialogContent>
<Dialog
open={renameDialogOpen}
onClose={() => setRenameDialogOpen(false)}
className="rename-dialog"
>
<DialogTitle className="dialog-title">Rename Poster</DialogTitle>
<DialogContent className="dialog-content">
<TextField
autoFocus
margin="dense"
@@ -223,7 +266,7 @@ const Cart = () => {
onChange={(e) => setNewPosterName(e.target.value)}
/>
</DialogContent>
<DialogActions>
<DialogActions className="dialog-actions">
<Button onClick={() => setRenameDialogOpen(false)} color="primary">
Cancel
</Button>

View File

@@ -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,13 +115,13 @@ 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}>
<Box className="loading-container">
<CircularProgress />
</Box>
) : (
@@ -129,20 +129,20 @@ const PosterGallery = ({ movieId }) => {
{posters.map((poster, index) => (
<Grid item xs={12} sm={6} md={4} lg={3} key={index}>
<Card
className={`${classes.posterCard} ${
className={`poster-card ${
selectedPosters.includes(poster.file_path)
? classes.selectedPoster
? "selected-poster"
: ""
}`}
onClick={() => handlePosterSelect(poster.file_path)}
>
<CardMedia
className={classes.posterImage}
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={classes.checkIcon} />
<CheckIcon className="check-icon" />
)}
</Card>
</Grid>

View File

@@ -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,32 +144,34 @@ 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}>
<Typography variant="h6" className="username">
Letterboxd User: {username}
</Typography>
)}
{loading ? (
<Box className={classes.progressContainer}>
<LinearProgress variant="determinate" value={progress} />
<Typography className={classes.progressLabel}>
<Box className="progress-container">
<LinearProgress />
<Typography className="progress-label">
Downloading movies...
</Typography>
</Box>
) : movies.length > 0 ? (
<>
<List className={classes.list}>
<List className="movies-list">
{movies.map((movie, index) => (
<ListItem
button
key={index}
onClick={() => handleMovieClick(movie.Name, movie.Year)}
className={classes.listItem}
className="movie-item"
>
<Grid container alignItems="center">
<Grid item>
@@ -302,26 +181,19 @@ const PosterSelector = () => {
`/api/placeholder/50/75`
}
alt={movie.Name}
className={classes.poster}
className="movie-poster"
/>
</Grid>
<Grid item className={classes.movieInfo}>
<Typography
variant="subtitle1"
className={classes.movieTitle}
>
<Grid item className="movie-info">
<Typography variant="subtitle1" className="movie-title">
{movie.Name}
</Typography>
<Typography
variant="body2"
color="textSecondary"
className={classes.movieYear}
>
<Typography variant="body2" className="movie-year">
{movie.Year}
</Typography>
</Grid>
<Grid item>
<Box className={classes.watchedDate}>
<Box className="watched-date">
{(() => {
const { day, month } = formatWatchedDate(
movie["Watched Date"]
@@ -330,13 +202,13 @@ const PosterSelector = () => {
<>
<Typography
variant="body2"
className={classes.watchedDay}
className="watched-day"
>
{day}
</Typography>
<Typography
variant="body2"
className={classes.watchedMonth}
className="watched-month"
>
{month}
</Typography>
@@ -349,7 +221,7 @@ const PosterSelector = () => {
</ListItem>
))}
</List>
<Box className={classes.paginationContainer}>
<Box className="pagination-container">
<ThemeProvider theme={paginationTheme}>
<Pagination
count={totalPages}
@@ -361,13 +233,13 @@ const PosterSelector = () => {
</Box>
</>
) : (
<Box className={classes.noMoviesContainer}>
<div className={classes.gifContainer}>
<div className={classes.circleBackground}></div>
<Box className="no-movies-container">
<div className="gif-container">
<div className="circle-background"></div>
<img
src={pulpGif}
alt="Confused reaction"
className={classes.gif}
className="reaction-gif"
/>
</div>
<Typography variant="body1" gutterBottom>
@@ -376,7 +248,7 @@ const PosterSelector = () => {
</Typography>
<Button
variant="contained"
className={classes.backButton}
className="back-button"
onClick={handleGoBack}
>
Go Back

View File

@@ -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 />

View File

@@ -1,3 +1,8 @@
@use "./scss/uploadDiary";
@use "./scss/posterSelector";
@use "./scss/posterGallery";
@use "./scss/cart";
.App {
text-align: center;
}

View File

@@ -16,6 +16,7 @@ code {
:root {
--background: #fff;
--bgComp: #1c1f23;
--foreground: #2c3e50;
--primary: #1caff2;
--secondary: #000;

166
src/styles/scss/_cart.scss Normal file
View 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%);
}
}
}
}
}
}

View 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;
}
}

View 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;
}
}
}

View 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;
}
}