diff --git a/.env b/.env new file mode 100644 index 0000000..929af06 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +REACT_APP_API_URL=https://letterboxd-diary-posters-picker-backend.onrender.com diff --git a/package-lock.json b/package-lock.json index 69a1e58..99c6737 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 45b7bb1..d93fb0d 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/App.js b/src/App.js index aee739d..f8aaafd 100644 --- a/src/App.js +++ b/src/App.js @@ -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"; diff --git a/src/components/Cart.js b/src/components/Cart.js index 4fcbfa6..9cdda35 100644 --- a/src/components/Cart.js +++ b/src/components/Cart.js @@ -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 ( - -
- Your Cart - {selectedPosters.map((poster, index) => ( - - - - {`Movie ID: ${poster.movieId}`} - {poster.posterId} - { - const newSelected = [...selectedForDownload]; - newSelected[index] = e.target.checked; - setSelectedForDownload(newSelected); - }} - className={classes.checkbox} - /> - } - label="Select for download" - className={classes.typography} - /> - - - - - ))} -
- -
- Download Format - setDownloadFormat(e.target.value)} - SelectProps={{ - native: true, - }} - className={classes.formControl} - > - - - - - - -
-
- setRenameDialogOpen(false)}> - Rename Poster - - setNewPosterName(e.target.value)} - /> - - - - - - -
+ 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 ( + +
+ + Your Cart + + {selectedPosters.map((poster, index) => ( + + + + + {`Movie ID: ${poster.movieId}`} + + + {poster.posterId} + + { + const newSelected = [...selectedForDownload]; + newSelected[index] = e.target.checked; + setSelectedForDownload(newSelected); + }} + className="checkbox" + /> + } + label="Select for download" + /> + + + + + ))} +
+ +
+ + Download Format + + setDownloadFormat(e.target.value)} + SelectProps={{ + native: true, + }} + className="form-control" + > + + + + + + +
+
+ setRenameDialogOpen(false)} + className="rename-dialog" + > + Rename Poster + + setNewPosterName(e.target.value)} + /> + + + + + + +
+ ); }; -export default Cart; \ No newline at end of file +export default Cart; diff --git a/src/components/PosterGallery.js b/src/components/PosterGallery.js index 40d642c..a616a2a 100644 --- a/src/components/PosterGallery.js +++ b/src/components/PosterGallery.js @@ -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 ( <> - - - + + + Posters for {movieName} ({movieYear}) {loading ? ( - - - + + + ) : ( - - {posters.map((poster, index) => ( - - handlePosterSelect(poster.file_path)} - > - - {selectedPosters.includes(poster.file_path) && ( - - )} - - - ))} - + + {posters.map((poster, index) => ( + + handlePosterSelect(poster.file_path)} + > + + {selectedPosters.includes(poster.file_path) && ( + + )} + + + ))} + )} diff --git a/src/components/PosterSelector.js b/src/components/PosterSelector.js index bd59970..0f24b50 100644 --- a/src/components/PosterSelector.js +++ b/src/components/PosterSelector.js @@ -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 ( <> - - - + + + Your diary + {username && ( - - Letterboxd User: {username} - + + Letterboxd User: {username} + )} + {loading ? ( - - - - Downloading movies... - - + + + + Downloading movies... + + ) : movies.length > 0 ? ( - <> - - {movies.map((movie, index) => ( - handleMovieClick(movie.Name, movie.Year)} - className={classes.listItem} - > - - - {movie.Name} - - - - {movie.Name} - - - {movie.Year} - - - - - {(() => { - const { day, month } = formatWatchedDate( - movie["Watched Date"] - ); - return ( - <> - - {day} - - - {month} - - - ); - })()} - - - - - ))} - - - - - - - - ) : ( - -
-
- Confused reaction + + {movies.map((movie, index) => ( + handleMovieClick(movie.Name, movie.Year)} + className="movie-item" + > + + + {movie.Name} + + + + {movie.Name} + + + {movie.Year} + + + + + {(() => { + const { day, month } = formatWatchedDate( + movie["Watched Date"] + ); + return ( + <> + + {day} + + + {month} + + + ); + })()} + + + + + ))} + + + + -
- - No movies found in your diary. Please check your Letterboxd - username or CSV file. - - +
+ + ) : ( + +
+
+ Confused reaction +
+ + No movies found in your diary. Please check your Letterboxd + username or CSV file. + + +
)}
diff --git a/src/components/UploadDiary.js b/src/components/UploadDiary.js index e1912fc..0899f90 100644 --- a/src/components/UploadDiary.js +++ b/src/components/UploadDiary.js @@ -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 ( - -
- + +
+ Upload Letterboxd Diary { onChange={(e) => setUsername(e.target.value)} placeholder="Enter your Letterboxd username" /> - + Or upload a CSV file: -
+
{file ? ( <> - - + + Your file has been uploaded: {file.name} ) : ( <> - - + + Drag and drop a CSV file here or click to select )}
- + {uploading ? ( ) : (
- + This tool allows you to easily import your Letterboxd diary and select your favorite movie poster. Follow these simple steps:
diff --git a/src/styles/App.css b/src/styles/App.scss similarity index 84% rename from src/styles/App.css rename to src/styles/App.scss index 7abaef0..18a6c31 100644 --- a/src/styles/App.css +++ b/src/styles/App.scss @@ -1,3 +1,8 @@ +@use "./scss/uploadDiary"; +@use "./scss/posterSelector"; +@use "./scss/posterGallery"; +@use "./scss/cart"; + .App { text-align: center; } diff --git a/src/styles/index.css b/src/styles/index.css index 89225c3..045a3e9 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -16,6 +16,7 @@ code { :root { --background: #fff; + --bgComp: #1c1f23; --foreground: #2c3e50; --primary: #1caff2; --secondary: #000; diff --git a/src/styles/scss/_cart.scss b/src/styles/scss/_cart.scss new file mode 100644 index 0000000..f12f1b0 --- /dev/null +++ b/src/styles/scss/_cart.scss @@ -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%); + } + } + } + } + } +} diff --git a/src/styles/scss/_posterGallery.scss b/src/styles/scss/_posterGallery.scss new file mode 100644 index 0000000..cb8cb1f --- /dev/null +++ b/src/styles/scss/_posterGallery.scss @@ -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; + } +} diff --git a/src/styles/scss/_posterSelector.scss b/src/styles/scss/_posterSelector.scss new file mode 100644 index 0000000..94f03e9 --- /dev/null +++ b/src/styles/scss/_posterSelector.scss @@ -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; + } + } +} diff --git a/src/styles/scss/_uploadDiary.scss b/src/styles/scss/_uploadDiary.scss new file mode 100644 index 0000000..2455d56 --- /dev/null +++ b/src/styles/scss/_uploadDiary.scss @@ -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; + } +}