From 9da083b6d0647dcd1a271259883604f1e76fc166 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Tue, 10 Jun 2025 14:47:52 -0700 Subject: [PATCH 01/28] Finished searching and sorting --- .env | 1 + package-lock.json | 671 +++++++++++++++------------- src/App.css | 12 + src/App.jsx | 18 +- src/LoadMoreBar.css | 14 + src/LoadMoreBar.jsx | 11 + src/MovieBox.css | 4 + src/MovieBox.jsx | 125 ++++++ src/MovieCard.css | 6 + src/MovieCard.jsx | 13 + src/MovieList.css | 7 + src/MovieList.jsx | 15 + src/MovieModal.css | 0 src/MovieModal.jsx | 0 src/SearchBar.css | 0 src/SearchBar.jsx | 25 ++ src/SideBar.css | 22 + src/SideBar.jsx | 22 + src/assets/movie-projector-logo.jpg | Bin 0 -> 21729 bytes src/index.css | 2 +- 20 files changed, 651 insertions(+), 317 deletions(-) create mode 100644 .env create mode 100644 src/LoadMoreBar.css create mode 100644 src/LoadMoreBar.jsx create mode 100644 src/MovieBox.css create mode 100644 src/MovieBox.jsx create mode 100644 src/MovieCard.css create mode 100644 src/MovieCard.jsx create mode 100644 src/MovieList.css create mode 100644 src/MovieList.jsx create mode 100644 src/MovieModal.css create mode 100644 src/MovieModal.jsx create mode 100644 src/SearchBar.css create mode 100644 src/SearchBar.jsx create mode 100644 src/SideBar.css create mode 100644 src/SideBar.jsx create mode 100644 src/assets/movie-projector-logo.jpg diff --git a/.env b/.env new file mode 100644 index 00000000..0864757a --- /dev/null +++ b/.env @@ -0,0 +1 @@ +VITE_APP_API_KEY=eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkZGNmMGFlODhlOTY3NWRmMjE2NzE3MDVmM2NmMjkwNCIsIm5iZiI6MTc0OTUwNTU2NS4wNTYsInN1YiI6IjY4NDc1NjFkY2YxMDYwNmU1YjFlYWFhOCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.sWQ7x0l5lNvEmG4q6DheipSeFpWjC_H3TA9OQDWqjcs \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 92a683d2..05b8ad18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,13 +45,15 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.2", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -226,19 +228,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -253,39 +257,28 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", - "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/types": "^7.27.3" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", - "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -324,14 +317,15 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -359,27 +353,28 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -389,13 +384,14 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -405,13 +401,14 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -421,13 +418,14 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -437,13 +435,14 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -453,13 +452,14 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -469,13 +469,14 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -485,13 +486,14 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -501,13 +503,14 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -517,13 +520,14 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -533,13 +537,14 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -549,13 +554,14 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -565,13 +571,14 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -581,13 +588,14 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -597,13 +605,14 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -613,13 +622,14 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -629,13 +639,14 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -645,13 +656,14 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -661,13 +673,14 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -677,13 +690,14 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -693,13 +707,14 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -709,13 +724,14 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -725,13 +741,14 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -928,195 +945,280 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz", - "integrity": "sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.42.0.tgz", + "integrity": "sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz", - "integrity": "sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.42.0.tgz", + "integrity": "sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz", - "integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.42.0.tgz", + "integrity": "sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz", - "integrity": "sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.42.0.tgz", + "integrity": "sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.42.0.tgz", + "integrity": "sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.42.0.tgz", + "integrity": "sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz", - "integrity": "sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.42.0.tgz", + "integrity": "sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.42.0.tgz", + "integrity": "sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz", - "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.42.0.tgz", + "integrity": "sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz", - "integrity": "sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.42.0.tgz", + "integrity": "sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.42.0.tgz", + "integrity": "sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz", - "integrity": "sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.42.0.tgz", + "integrity": "sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA==", "cpu": [ - "ppc64le" + "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz", - "integrity": "sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.42.0.tgz", + "integrity": "sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.42.0.tgz", + "integrity": "sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz", - "integrity": "sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.42.0.tgz", + "integrity": "sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz", - "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.42.0.tgz", + "integrity": "sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz", - "integrity": "sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.42.0.tgz", + "integrity": "sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz", - "integrity": "sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.42.0.tgz", + "integrity": "sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz", - "integrity": "sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.42.0.tgz", + "integrity": "sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz", - "integrity": "sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.42.0.tgz", + "integrity": "sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1164,10 +1266,11 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" }, "node_modules/@types/prop-types": { "version": "15.7.12", @@ -1265,18 +1368,6 @@ "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1533,35 +1624,6 @@ } ] }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1575,10 +1637,11 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1879,11 +1942,12 @@ } }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -1891,29 +1955,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -1925,15 +1989,6 @@ "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/eslint": { "version": "8.57.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", @@ -2334,6 +2389,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2506,15 +2562,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -3160,9 +3207,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -3170,6 +3217,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -3402,10 +3450,11 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/possible-typed-array-names": { "version": "1.0.0", @@ -3417,9 +3466,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", + "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", "dev": true, "funding": [ { @@ -3435,10 +3484,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -3622,12 +3672,13 @@ } }, "node_modules/rollup": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.2.tgz", - "integrity": "sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.42.0.tgz", + "integrity": "sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw==", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.7" }, "bin": { "rollup": "dist/bin/rollup" @@ -3637,21 +3688,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.2", - "@rollup/rollup-android-arm64": "4.13.2", - "@rollup/rollup-darwin-arm64": "4.13.2", - "@rollup/rollup-darwin-x64": "4.13.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.2", - "@rollup/rollup-linux-arm64-gnu": "4.13.2", - "@rollup/rollup-linux-arm64-musl": "4.13.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2", - "@rollup/rollup-linux-riscv64-gnu": "4.13.2", - "@rollup/rollup-linux-s390x-gnu": "4.13.2", - "@rollup/rollup-linux-x64-gnu": "4.13.2", - "@rollup/rollup-linux-x64-musl": "4.13.2", - "@rollup/rollup-win32-arm64-msvc": "4.13.2", - "@rollup/rollup-win32-ia32-msvc": "4.13.2", - "@rollup/rollup-win32-x64-msvc": "4.13.2", + "@rollup/rollup-android-arm-eabi": "4.42.0", + "@rollup/rollup-android-arm64": "4.42.0", + "@rollup/rollup-darwin-arm64": "4.42.0", + "@rollup/rollup-darwin-x64": "4.42.0", + "@rollup/rollup-freebsd-arm64": "4.42.0", + "@rollup/rollup-freebsd-x64": "4.42.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.42.0", + "@rollup/rollup-linux-arm-musleabihf": "4.42.0", + "@rollup/rollup-linux-arm64-gnu": "4.42.0", + "@rollup/rollup-linux-arm64-musl": "4.42.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.42.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.42.0", + "@rollup/rollup-linux-riscv64-gnu": "4.42.0", + "@rollup/rollup-linux-riscv64-musl": "4.42.0", + "@rollup/rollup-linux-s390x-gnu": "4.42.0", + "@rollup/rollup-linux-x64-gnu": "4.42.0", + "@rollup/rollup-linux-x64-musl": "4.42.0", + "@rollup/rollup-win32-arm64-msvc": "4.42.0", + "@rollup/rollup-win32-ia32-msvc": "4.42.0", + "@rollup/rollup-win32-x64-msvc": "4.42.0", "fsevents": "~2.3.2" } }, @@ -3802,10 +3858,11 @@ } }, "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==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -3909,18 +3966,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -3939,15 +3984,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4100,14 +4136,15 @@ } }, "node_modules/vite": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz", - "integrity": "sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==", + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -4126,6 +4163,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -4143,6 +4181,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/src/App.css b/src/App.css index 0bf65669..b8432304 100644 --- a/src/App.css +++ b/src/App.css @@ -1,5 +1,12 @@ .App { text-align: center; + display: flex; + flex-direction: row; +} + +main { + display: flex; + flex-direction: column; } .App-header { @@ -12,6 +19,11 @@ padding: 20px; } +.footer { + width: 100vw; + background-color: #888; +} + @media (max-width: 600px) { .movie-card { width: 100%; diff --git a/src/App.jsx b/src/App.jsx index dfa91584..dfa7cc47 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,10 +1,26 @@ import { useState } from 'react' import './App.css' +import SideBar from './SideBar' +import MovieBox from './MovieBox' const App = () => { + // const [mode, setMode] = useState("home") + + const handleModeChange = ({newMode}) => { + // console.log("new mode: " + newMode) + // setMode(newMode) + } + return (
- + +
+

Flixster

+ +
+

By Jack McClure 2025

+
+
) } diff --git a/src/LoadMoreBar.css b/src/LoadMoreBar.css new file mode 100644 index 00000000..cf9165a1 --- /dev/null +++ b/src/LoadMoreBar.css @@ -0,0 +1,14 @@ +.loadmore-bar { + width: 100%; + background-color: #888; + height: 80px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.loadmore-button { + height: 40px; + width: 160px; +} \ No newline at end of file diff --git a/src/LoadMoreBar.jsx b/src/LoadMoreBar.jsx new file mode 100644 index 00000000..4ab9c135 --- /dev/null +++ b/src/LoadMoreBar.jsx @@ -0,0 +1,11 @@ +import './LoadMoreBar.css' + +const LoadMoreBar = ( {loadMoreHandler} ) => { + return ( +
+ +
+ ) +} + +export default LoadMoreBar \ No newline at end of file diff --git a/src/MovieBox.css b/src/MovieBox.css new file mode 100644 index 00000000..e557842f --- /dev/null +++ b/src/MovieBox.css @@ -0,0 +1,4 @@ +.moviebox { + display: flex; + flex-direction: column; +} \ No newline at end of file diff --git a/src/MovieBox.jsx b/src/MovieBox.jsx new file mode 100644 index 00000000..fc79242c --- /dev/null +++ b/src/MovieBox.jsx @@ -0,0 +1,125 @@ +import SearchBar from './SearchBar' +import MovieList from './MovieList' +import LoadMoreBar from './LoadMoreBar' +import { useEffect, useState } from 'react' + + + +const MovieBox = () => { + const [movies, setMovies] = useState([]) + const [page, setPage] = useState(1) + const [morePages, setMorePages] = useState(true) + const [searchQuery, setSearchQuery] = useState("") + const [sortMode, setSortMode] = useState("none") + const fetchMovies = async () => { + try { + const apiKey = import.meta.env.VITE_APP_API_KEY + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: `Bearer ${apiKey}` + } + }; + let response = null + if (searchQuery === "") { + response = await fetch(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${page}&include_adult=false`, options) + } else { + response = await fetch(`https://api.themoviedb.org/3/search/movie?query=${searchQuery}&include_adult=false&language=en-US&page=${page}`, options) + } + if (!response.ok) { + throw new Error('Failed to fetch movies') + } + const data = await response.json() + if (data.total_pages === page) { + setMorePages(false) + } + // console.log([...movies, ...data.results]) + setMovies([...movies, ...data.results]) + } catch (error) { + console.error(error) + } + } + + const sortMovies = () => { + // Was creating reference to movies, not a copy, so it wasn't re-rendering + // Destructuring makes a copy, will recognize sortedMovies as new + // So setMovies(sortedMovies) triggers re-render + if (movies.length === 0) { + return; + } + let sortedMovies = [...movies] + if (sortMode === "title") { + sortedMovies.sort((left, right) => { + if (left.title < right.title) {return -1;} + if (left.title > right.title) {return 1;} + return 0; + }) + } else if (sortMode === "release") { + // Listened to feedback about shortening sorting comparator + // Note: can't subtract strings/titles in JS + sortedMovies.sort((left, right) => { + left.year = parseInt(left.release_date.slice(0, 4)) + right.year = parseInt(right.release_date.slice(0, 4)) + left.month = parseInt(left.release_date.slice(5, 7)) + right.month = parseInt(right.release_date.slice(5, 7)) + left.day = parseInt(left.release_date.slice(8, 10)) + right.day = parseInt(right.release_date.slice(8, 10)) + if (!(right.year === left.year)) { + return right.year - left.year + } else if (!(right.month === left.month)) { + return right.month - left.month + } else { + return right.day - left.day + } + + }) + } else if (sortMode === "vote") { + sortedMovies.sort((left, right) => right.vote_average-left.vote_average) + } + setMovies(sortedMovies) + } + + useEffect(() => { + fetchMovies() + sortMovies() + }, [page, searchQuery]) + + useEffect(() => { + sortMovies() + }, [sortMode]) + + const loadMore = () => { + setPage( (oldPage) => { + return oldPage + 1; + }) + } + + const updateSearchQuery = (term) => { + if (!(term === searchQuery)) { + setSortMode("none") + setMovies([]) + setPage(1) + setSearchQuery(term) + } + } + + const updateSortMode = (mode) => { + setSortMode(mode) + } + + const handleClear = () => { + updateSortMode("none") + updateSearchQuery("") + } + + return ( +
+ + + { (!movies.length == 0 || !morePages) ? : null } +
+ ) +} + +export default MovieBox \ No newline at end of file diff --git a/src/MovieCard.css b/src/MovieCard.css new file mode 100644 index 00000000..9fb525c5 --- /dev/null +++ b/src/MovieCard.css @@ -0,0 +1,6 @@ +.moviecard { + display: flex; + flex-direction: column; + width: 20%; + height: 40%; +} \ No newline at end of file diff --git a/src/MovieCard.jsx b/src/MovieCard.jsx new file mode 100644 index 00000000..6a421254 --- /dev/null +++ b/src/MovieCard.jsx @@ -0,0 +1,13 @@ +import './MovieCard.css' + +const MovieCard = ( {movie} ) => { + return ( +
+ +

{movie.title}

+

{movie.vote_average}

+
+ ) +} + +export default MovieCard \ No newline at end of file diff --git a/src/MovieList.css b/src/MovieList.css new file mode 100644 index 00000000..cc8c7ccb --- /dev/null +++ b/src/MovieList.css @@ -0,0 +1,7 @@ +.movielist { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 30px; + margin: 30px; +} \ No newline at end of file diff --git a/src/MovieList.jsx b/src/MovieList.jsx new file mode 100644 index 00000000..904f261d --- /dev/null +++ b/src/MovieList.jsx @@ -0,0 +1,15 @@ +import MovieCard from './MovieCard' +import './MovieList.css' + +const MovieList = ( {movies} ) => { + return ( +
+ { movies.length === 0 ?

No movies found!

: + movies.map( (movie) => { + return ; + })} +
+ ) +} + +export default MovieList \ No newline at end of file diff --git a/src/MovieModal.css b/src/MovieModal.css new file mode 100644 index 00000000..e69de29b diff --git a/src/MovieModal.jsx b/src/MovieModal.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/SearchBar.css b/src/SearchBar.css new file mode 100644 index 00000000..e69de29b diff --git a/src/SearchBar.jsx b/src/SearchBar.jsx new file mode 100644 index 00000000..5ce0b002 --- /dev/null +++ b/src/SearchBar.jsx @@ -0,0 +1,25 @@ +import './SearchBar.css' +import { useState } from 'react' + +const SearchBar = ({ searchQuery, searchHandler, sortMode, sortHandler, clearHandler }) => { + const [searchText, setSearchText] = useState("") + return ( +
+
+ setSearchText(e.target.value)} value={searchText} placeholder="Search..."> + + { !(searchQuery === "") ? : null } +
+
+ +
+
+ ) +} + +export default SearchBar \ No newline at end of file diff --git a/src/SideBar.css b/src/SideBar.css new file mode 100644 index 00000000..729d2a82 --- /dev/null +++ b/src/SideBar.css @@ -0,0 +1,22 @@ +.sidebar { + display: flex; + flex-direction: column; + gap: 40px; +} + +.sidebar-image { + object-fit: contain; + width: 15vw; + height: 15vh; +} + +.sidebar-links { + display: flex; + flex-direction: column; + gap: 30px; +} + +.sidebar-links > a:hover { + cursor: pointer; + text-decoration: underline; +} \ No newline at end of file diff --git a/src/SideBar.jsx b/src/SideBar.jsx new file mode 100644 index 00000000..f2610d29 --- /dev/null +++ b/src/SideBar.jsx @@ -0,0 +1,22 @@ +import './SideBar.css' + +const SideBarLinks = ({modeHandler}) => { + return ( +
+ Now Playing + Favorites + Watched +
+ ) +} + +const SideBar = ({modeHandler}) => { + return ( +
+ + +
+ ) +} + +export default SideBar \ No newline at end of file diff --git a/src/assets/movie-projector-logo.jpg b/src/assets/movie-projector-logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b77454c37fed207b99f2956ffeb1bcc1a4d5d59d GIT binary patch literal 21729 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 Ptu#E4cj3GHuc`k6M^wTp literal 0 HcmV?d00001 diff --git a/src/index.css b/src/index.css index e1faed1a..81744e63 100644 --- a/src/index.css +++ b/src/index.css @@ -16,4 +16,4 @@ button { button:hover { background-color: #777; color: white; -} +} \ No newline at end of file From 5b966bdd95938f7b53067dba98cfc2f734749203 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Tue, 10 Jun 2025 17:23:48 -0700 Subject: [PATCH 02/28] Finished modal, add modal content --- src/App.jsx | 3 ++- src/MovieBox.jsx | 61 ++++++++++++++++++++++++++-------------------- src/MovieCard.css | 9 +++++++ src/MovieCard.jsx | 19 ++++++++++++--- src/MovieList.jsx | 7 +++--- src/MovieModal.css | 40 ++++++++++++++++++++++++++++++ src/MovieModal.jsx | 46 ++++++++++++++++++++++++++++++++++ 7 files changed, 152 insertions(+), 33 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index dfa7cc47..67297f6a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -4,12 +4,13 @@ import SideBar from './SideBar' import MovieBox from './MovieBox' const App = () => { - // const [mode, setMode] = useState("home") + const [mode, setMode] = useState("home") const handleModeChange = ({newMode}) => { // console.log("new mode: " + newMode) // setMode(newMode) } + return (
diff --git a/src/MovieBox.jsx b/src/MovieBox.jsx index fc79242c..273af2c6 100644 --- a/src/MovieBox.jsx +++ b/src/MovieBox.jsx @@ -6,11 +6,13 @@ import { useEffect, useState } from 'react' const MovieBox = () => { - const [movies, setMovies] = useState([]) + const [movies, setMovies] = useState({}) + const [order, setOrder] = useState([]) const [page, setPage] = useState(1) const [morePages, setMorePages] = useState(true) const [searchQuery, setSearchQuery] = useState("") const [sortMode, setSortMode] = useState("none") + const fetchMovies = async () => { try { const apiKey = import.meta.env.VITE_APP_API_KEY @@ -34,8 +36,16 @@ const MovieBox = () => { if (data.total_pages === page) { setMorePages(false) } - // console.log([...movies, ...data.results]) - setMovies([...movies, ...data.results]) + let newMovies = {} + let newOrder = [] + data.results.map( (movie) => { + newMovies[movie.id] = movie + newOrder.push(movie.id) + }) + newMovies = {...movies, ...newMovies} + setMovies(newMovies) + newOrder = [...order, ...newOrder] + setOrder(newOrder) } catch (error) { console.error(error) } @@ -48,36 +58,35 @@ const MovieBox = () => { if (movies.length === 0) { return; } - let sortedMovies = [...movies] + // Split movies dict into keys (id) and values (movie) + let movieEntries = Object.entries(movies) if (sortMode === "title") { - sortedMovies.sort((left, right) => { + movieEntries.sort((left_entry, right_entry) => { + const left = left_entry[1] + const right = right_entry[1] if (left.title < right.title) {return -1;} if (left.title > right.title) {return 1;} return 0; }) } else if (sortMode === "release") { // Listened to feedback about shortening sorting comparator - // Note: can't subtract strings/titles in JS - sortedMovies.sort((left, right) => { - left.year = parseInt(left.release_date.slice(0, 4)) - right.year = parseInt(right.release_date.slice(0, 4)) - left.month = parseInt(left.release_date.slice(5, 7)) - right.month = parseInt(right.release_date.slice(5, 7)) - left.day = parseInt(left.release_date.slice(8, 10)) - right.day = parseInt(right.release_date.slice(8, 10)) - if (!(right.year === left.year)) { - return right.year - left.year - } else if (!(right.month === left.month)) { - return right.month - left.month - } else { - return right.day - left.day - } - + // Note: can't subtract strings in JS + movieEntries.sort((left_entry, right_entry) => { + const left = left_entry[1] + const right = right_entry[1] + left.date = new Date(left.release_date) + right.date = new Date(right.release_date) + return right.date - left.date }) } else if (sortMode === "vote") { - sortedMovies.sort((left, right) => right.vote_average-left.vote_average) + movieEntries.sort((left_entry, right_entry) => { + const left = left_entry[1] + const right = right_entry[1] + return right.vote_average-left.vote_average + }) } - setMovies(sortedMovies) + const newOrder = movieEntries.map( (entry) => entry[0]) + setOrder(newOrder) } useEffect(() => { @@ -98,7 +107,7 @@ const MovieBox = () => { const updateSearchQuery = (term) => { if (!(term === searchQuery)) { setSortMode("none") - setMovies([]) + setMovies({}) setPage(1) setSearchQuery(term) } @@ -116,8 +125,8 @@ const MovieBox = () => { return (
- - { (!movies.length == 0 || !morePages) ? : null } + + { (!Object.values(movies).length == 0 || !morePages) ? : null }
) } diff --git a/src/MovieCard.css b/src/MovieCard.css index 9fb525c5..86229562 100644 --- a/src/MovieCard.css +++ b/src/MovieCard.css @@ -3,4 +3,13 @@ flex-direction: column; width: 20%; height: 40%; +} + +.moviecard-clickable { + display: flex; + flex-direction: column; +} + +.moviecard-clickable:hover { + cursor: pointer; } \ No newline at end of file diff --git a/src/MovieCard.jsx b/src/MovieCard.jsx index 6a421254..aaf6407a 100644 --- a/src/MovieCard.jsx +++ b/src/MovieCard.jsx @@ -1,11 +1,24 @@ import './MovieCard.css' +import { useState } from 'react' +import MovieModal from './MovieModal' const MovieCard = ( {movie} ) => { + const [showModal, setShowModal] = useState(false) + + const closeModal = (e) => { + if (e.currentTarget === e.target) { + setShowModal(false) + } + } + return (
- -

{movie.title}

-

{movie.vote_average}

+
setShowModal(true)}> + +

{movie.title}

+

{movie.vote_average}

+
+ { showModal ? : null}
) } diff --git a/src/MovieList.jsx b/src/MovieList.jsx index 904f261d..8f3a2e27 100644 --- a/src/MovieList.jsx +++ b/src/MovieList.jsx @@ -1,12 +1,13 @@ import MovieCard from './MovieCard' import './MovieList.css' -const MovieList = ( {movies} ) => { +const MovieList = ( {movies, order} ) => { return (
{ movies.length === 0 ?

No movies found!

: - movies.map( (movie) => { - return ; + order.map( (id) => { + // console.log(movies[id]) + return ; })}
) diff --git a/src/MovieModal.css b/src/MovieModal.css index e69de29b..d5770c22 100644 --- a/src/MovieModal.css +++ b/src/MovieModal.css @@ -0,0 +1,40 @@ +.moviemodal { + display: block; + position: fixed; + z-index: 2; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.4); +} + +.moviemodal-content { + background-color: #888; + margin: 100px auto; + padding: 20px; + border: 1px solid #888; + width: 75vw; + max-width: 100%; + height: 80%; + display:flex; + flex-direction: row; + position: relative; + border-radius: 8px; +} + +.moviemodal-close-button { + position: absolute; + right: 0; + top: 0; +} + +.moviemodal-content-image { + object-fit: contain; + height: 100%; + width: 40%; +} + +.moviemodal-content-info { + width: 60% +} \ No newline at end of file diff --git a/src/MovieModal.jsx b/src/MovieModal.jsx index e69de29b..9682bc8a 100644 --- a/src/MovieModal.jsx +++ b/src/MovieModal.jsx @@ -0,0 +1,46 @@ +import './MovieModal.css' +import { useState, useEffect } from 'react' + +const MovieModal = ({movieID, closeModal}) => { + const [movie, setMovie] = useState({}) + + const fetchMovie = async (movieID) => { + try { + const apiKey = import.meta.env.VITE_APP_API_KEY + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: `Bearer ${apiKey}` + } + }; + let response = null + response = await fetch(`https://api.themoviedb.org/3/movie/${movieID}`, options) + if (!response.ok) { + throw new Error('Failed to fetch movies') + } + const data = await response.json() + setMovie(data) + } catch (error) { + console.error(error) + } + } + + useEffect( () => { + fetchMovie(movieID) + }, []) + return ( +
closeModal(e)}> +
+ + +
+

{movie.title}

+

{movie.vote_average}

+
+
+
+ ) +} + +export default MovieModal \ No newline at end of file From ced1334faf720300c4aab76c61afd047a70914fb Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Tue, 10 Jun 2025 22:13:07 -0700 Subject: [PATCH 03/28] Added trailers viewer --- src/App.css | 38 ++++++++++++++++++++++++++-- src/App.jsx | 14 ++++++++--- src/MovieBox.jsx | 8 ++++-- src/MovieList.css | 1 + src/MovieList.jsx | 2 +- src/MovieModal.css | 21 +++++++++++++++- src/MovieModal.jsx | 63 +++++++++++++++++++++++++++++++++++++++++++--- src/SideBar.jsx | 1 - 8 files changed, 134 insertions(+), 14 deletions(-) diff --git a/src/App.css b/src/App.css index b8432304..bdba5ee3 100644 --- a/src/App.css +++ b/src/App.css @@ -19,8 +19,42 @@ main { padding: 20px; } -.footer { - width: 100vw; +header { + display: flex; + flex-direction: row; + height: 200px; + width: 100%; + align-items: center; + justify-content: center; +} + +.header-text { + display:flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +.flixster-title { + font-size: 60px; + margin-bottom: 20px; +} + +.banner { + font-size: 30px; +} + +.header-image { + position: absolute; + top: 10px; + left: 10px; + object-fit: contain; + height: 200px; + width: 25%; +} + +footer { + width: 100%; background-color: #888; } diff --git a/src/App.jsx b/src/App.jsx index 67297f6a..2a983515 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -14,13 +14,19 @@ const App = () => { return (
-
-

Flixster

+
+ +
+

Flixster

+ {/* Says banner component unrecognized in this browser */} +
Welcome to Flixster!
+
+
-
+

By Jack McClure 2025

-
+
) diff --git a/src/MovieBox.jsx b/src/MovieBox.jsx index 273af2c6..e6eda074 100644 --- a/src/MovieBox.jsx +++ b/src/MovieBox.jsx @@ -38,9 +38,12 @@ const MovieBox = () => { } let newMovies = {} let newOrder = [] + // TMDB seems to be giving 1-2 repeats from the end of the last page on load more data.results.map( (movie) => { - newMovies[movie.id] = movie - newOrder.push(movie.id) + if (!order.includes(movie.id) && !newOrder.includes(movie.id)) { + newMovies[movie.id] = movie + newOrder.push(movie.id) + } }) newMovies = {...movies, ...newMovies} setMovies(newMovies) @@ -107,6 +110,7 @@ const MovieBox = () => { const updateSearchQuery = (term) => { if (!(term === searchQuery)) { setSortMode("none") + setOrder([]) setMovies({}) setPage(1) setSearchQuery(term) diff --git a/src/MovieList.css b/src/MovieList.css index cc8c7ccb..f47ca435 100644 --- a/src/MovieList.css +++ b/src/MovieList.css @@ -4,4 +4,5 @@ flex-wrap: wrap; gap: 30px; margin: 30px; + justify-content: center; } \ No newline at end of file diff --git a/src/MovieList.jsx b/src/MovieList.jsx index 8f3a2e27..8f2f074d 100644 --- a/src/MovieList.jsx +++ b/src/MovieList.jsx @@ -5,7 +5,7 @@ const MovieList = ( {movies, order} ) => { return (
{ movies.length === 0 ?

No movies found!

: - order.map( (id) => { + movies && order && order.map( (id) => { // console.log(movies[id]) return ; })} diff --git a/src/MovieModal.css b/src/MovieModal.css index d5770c22..75f9d161 100644 --- a/src/MovieModal.css +++ b/src/MovieModal.css @@ -35,6 +35,25 @@ width: 40%; } +.moviemodal-content-right { + display:flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +iframe { + margin: 15px; +} + +.trailer-select { + display: flex; + flex-direction: row; + gap: 15px; + justify-content: center; +} + .moviemodal-content-info { width: 60% -} \ No newline at end of file +} + diff --git a/src/MovieModal.jsx b/src/MovieModal.jsx index 9682bc8a..e68a7f9f 100644 --- a/src/MovieModal.jsx +++ b/src/MovieModal.jsx @@ -3,6 +3,8 @@ import { useState, useEffect } from 'react' const MovieModal = ({movieID, closeModal}) => { const [movie, setMovie] = useState({}) + const [trailers, setTrailers] = useState([]) + const [currentTrailer, setCurrentTrailer] = useState(0) const fetchMovie = async (movieID) => { try { @@ -26,17 +28,72 @@ const MovieModal = ({movieID, closeModal}) => { } } + const fetchTrailers = async (movieID) => { + try { + const apiKey = import.meta.env.VITE_APP_API_KEY + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: `Bearer ${apiKey}` + } + }; + let response = null + response = await fetch(`https://api.themoviedb.org/3/movie/${movieID}/videos?language=en-US`, options) + if (!response.ok) { + throw new Error('Failed to fetch videos') + } + const data = await response.json() + const trailers = data.results.filter( (video) => {return video.type === "Trailer"}) + setTrailers(trailers) + } catch (error) { + console.error(error) + } + } + useEffect( () => { fetchMovie(movieID) + fetchTrailers(movieID) }, []) + + // console.log(trailers[currentTrailer].key) return (
closeModal(e)}>
-
-

{movie.title}

-

{movie.vote_average}

+
+ { trailers.length > 0 && trailers[currentTrailer] ? +
+

Number of trailers: {trailers.length}

+ +
+ +

{trailers[currentTrailer].name}

+ +
+
+ : null } +
+

{movie.title}

+

{movie.overview}

+

Runtime: {movie.runtime} minutes

+

Release Date: {movie.release_date}

+
+

Genres:

+ {movie.genres && movie.genres.map( (genreEntry) => { + return ( +

{genreEntry.name}

+ ) + })} +
+
diff --git a/src/SideBar.jsx b/src/SideBar.jsx index f2610d29..042bd487 100644 --- a/src/SideBar.jsx +++ b/src/SideBar.jsx @@ -13,7 +13,6 @@ const SideBarLinks = ({modeHandler}) => { const SideBar = ({modeHandler}) => { return (
-
) From 829ada022917377b215aeee6f478e1134d4095e5 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Wed, 11 Jun 2025 10:03:04 -0700 Subject: [PATCH 04/28] first deploy --- package-lock.json | 12 +++++++++++- package.json | 3 ++- src/App.css | 10 +++++----- src/App.jsx | 39 +++++++++++++++++++++------------------ src/MovieBox.css | 2 +- src/MovieBox.jsx | 30 ++++++++++++++++++++++++++---- src/MovieCard.css | 23 +++++++++++++++++++---- src/MovieCard.jsx | 9 +++++++-- src/MovieList.jsx | 14 ++++++++------ src/MovieModal.jsx | 3 +-- src/SideBar.css | 1 + src/SideBar.jsx | 7 ++++--- 12 files changed, 106 insertions(+), 47 deletions(-) diff --git a/package-lock.json b/package-lock.json index 05b8ad18..fea0bd08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.0", "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-icons": "^5.5.0" }, "devDependencies": { "@types/react": "^18.2.66", @@ -3566,6 +3567,15 @@ "react": "^18.2.0" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index eded5715..94e6025f 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ }, "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-icons": "^5.5.0" }, "devDependencies": { "@types/react": "^18.2.66", diff --git a/src/App.css b/src/App.css index bdba5ee3..46a1c1e0 100644 --- a/src/App.css +++ b/src/App.css @@ -4,11 +4,6 @@ flex-direction: row; } -main { - display: flex; - flex-direction: column; -} - .App-header { background-color: #282c34; display: flex; @@ -53,6 +48,11 @@ header { width: 25%; } +main { + display: flex; + flex-direction: row; +} + footer { width: 100%; background-color: #888; diff --git a/src/App.jsx b/src/App.jsx index 2a983515..68904848 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -4,30 +4,33 @@ import SideBar from './SideBar' import MovieBox from './MovieBox' const App = () => { - const [mode, setMode] = useState("home") + const [mode, setMode] = useState("now-playing") + const [showSidebar, setShowSidebar] = useState(true) - const handleModeChange = ({newMode}) => { - // console.log("new mode: " + newMode) - // setMode(newMode) + const handleModeChange = (newMode) => { + setMode(newMode) } return (
-
-
- -
-

Flixster

- {/* Says banner component unrecognized in this browser */} -
Welcome to Flixster!
-
-
- -
-

By Jack McClure 2025

-
-
+ + { showSidebar ? : null} +
+
+
+

Flixster

+ {/* Says banner component unrecognized in this browser */} +
Welcome to Flixster!
+
+
+
+ +
+
+

By Jack McClure 2025

+
+
) } diff --git a/src/MovieBox.css b/src/MovieBox.css index e557842f..8e71d3b8 100644 --- a/src/MovieBox.css +++ b/src/MovieBox.css @@ -1,4 +1,4 @@ .moviebox { display: flex; - flex-direction: column; + flex-direction: row; } \ No newline at end of file diff --git a/src/MovieBox.jsx b/src/MovieBox.jsx index e6eda074..d747644c 100644 --- a/src/MovieBox.jsx +++ b/src/MovieBox.jsx @@ -5,13 +5,15 @@ import { useEffect, useState } from 'react' -const MovieBox = () => { +const MovieBox = (mode) => { const [movies, setMovies] = useState({}) const [order, setOrder] = useState([]) const [page, setPage] = useState(1) const [morePages, setMorePages] = useState(true) const [searchQuery, setSearchQuery] = useState("") const [sortMode, setSortMode] = useState("none") + const [favorites, setFavorites] = useState([]) + const [watched, setWatched] = useState([]) const fetchMovies = async () => { try { @@ -126,11 +128,31 @@ const MovieBox = () => { updateSearchQuery("") } + const toggleFavorite = (movieID) => { + let newFavorites = [...favorites] + if (newFavorites.includes(movieID)) { + newFavorites.filter( (id) => {return id !== movieID}) + } else { + newFavorites.push(movieID) + } + console.log(favorites) + } + + const toggleWatched = (movieID) => { + let newWatched = [...watched] + if (newWatched.includes(movieID)) { + newWatched.filter( (id) => {return id !== movieID}) + } else { + newWatched.push(movieID) + } + console.log(watched) + } + return (
- - - { (!Object.values(movies).length == 0 || !morePages) ? : null } + { mode.mode === "now-playing" ? : null } + + { mode.mode === "now-playing" && (!Object.values(movies).length == 0 || !morePages) ? : null }
) } diff --git a/src/MovieCard.css b/src/MovieCard.css index 86229562..9926a569 100644 --- a/src/MovieCard.css +++ b/src/MovieCard.css @@ -2,14 +2,29 @@ display: flex; flex-direction: column; width: 20%; - height: 40%; + flex-basis: 20%; } -.moviecard-clickable { +.moviecard-modalclickable { display: flex; flex-direction: column; + height: 70%; + flex-grow: 1; } -.moviecard-clickable:hover { +.moviecard-modalclickable:hover { cursor: pointer; -} \ No newline at end of file +} + +.moviecard-title { + height: 30%; +} + +.moviecard-buttons { + position: relative; + bottom: 0; + display: flex; + flex-direction: row; + gap: 40%; + justify-content: center; +} diff --git a/src/MovieCard.jsx b/src/MovieCard.jsx index aaf6407a..81bf1db0 100644 --- a/src/MovieCard.jsx +++ b/src/MovieCard.jsx @@ -1,8 +1,9 @@ import './MovieCard.css' import { useState } from 'react' import MovieModal from './MovieModal' +import {RiCloseLine} from 'react-icons/ri' -const MovieCard = ( {movie} ) => { +const MovieCard = ( {id, movie, favorites, watched, toggleFavorite, toggleWatched } ) => { const [showModal, setShowModal] = useState(false) const closeModal = (e) => { @@ -13,11 +14,15 @@ const MovieCard = ( {movie} ) => { return (
-
setShowModal(true)}> +
setShowModal(true)}>

{movie.title}

{movie.vote_average}

+
+ + +
{ showModal ? : null}
) diff --git a/src/MovieList.jsx b/src/MovieList.jsx index 8f2f074d..0b87a666 100644 --- a/src/MovieList.jsx +++ b/src/MovieList.jsx @@ -1,14 +1,16 @@ import MovieCard from './MovieCard' import './MovieList.css' -const MovieList = ( {movies, order} ) => { +const MovieList = ( {movies, order, favorites, watched, toggleFavorite, toggleWatched} ) => { + // { mode === "now-playing" ? console.log(mode === "now-playing") : null } return (
- { movies.length === 0 ?

No movies found!

: - movies && order && order.map( (id) => { - // console.log(movies[id]) - return ; - })} + { + movies.length === 0 ?

No movies found!

: + movies && order && order.map( (id) => { + return ; + }) + }
) } diff --git a/src/MovieModal.jsx b/src/MovieModal.jsx index e68a7f9f..6b15316c 100644 --- a/src/MovieModal.jsx +++ b/src/MovieModal.jsx @@ -65,7 +65,6 @@ const MovieModal = ({movieID, closeModal}) => {
{ trailers.length > 0 && trailers[currentTrailer] ?
-

Number of trailers: {trailers.length}

-

{trailers[currentTrailer].name}

+

{trailers[currentTrailer].name} ({currentTrailer+1}/{trailers.length})

diff --git a/src/SideBar.css b/src/SideBar.css index 729d2a82..34ddcbb9 100644 --- a/src/SideBar.css +++ b/src/SideBar.css @@ -2,6 +2,7 @@ display: flex; flex-direction: column; gap: 40px; + width: 30%; } .sidebar-image { diff --git a/src/SideBar.jsx b/src/SideBar.jsx index 042bd487..c1471871 100644 --- a/src/SideBar.jsx +++ b/src/SideBar.jsx @@ -3,9 +3,9 @@ import './SideBar.css' const SideBarLinks = ({modeHandler}) => { return ( ) } @@ -13,6 +13,7 @@ const SideBarLinks = ({modeHandler}) => { const SideBar = ({modeHandler}) => { return (
+
) From 8b10dc4b73b4bc95705a8cb257880beefbffc9b9 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Wed, 11 Jun 2025 10:21:10 -0700 Subject: [PATCH 05/28] Trying to fix movie image for live deploy --- src/MovieCard.jsx | 7 ++++--- src/SearchBar.jsx | 2 +- src/SideBar.jsx | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/MovieCard.jsx b/src/MovieCard.jsx index 81bf1db0..3ed7bcd1 100644 --- a/src/MovieCard.jsx +++ b/src/MovieCard.jsx @@ -1,7 +1,8 @@ import './MovieCard.css' import { useState } from 'react' import MovieModal from './MovieModal' -import {RiCloseLine} from 'react-icons/ri' +import { BsEyeSlash, BsEyeFill } from "react-icons/bs"; +import { FaStar, FaRegStar } from "react-icons/fa"; const MovieCard = ( {id, movie, favorites, watched, toggleFavorite, toggleWatched } ) => { const [showModal, setShowModal] = useState(false) @@ -20,8 +21,8 @@ const MovieCard = ( {id, movie, favorites, watched, toggleFavorite, toggleWatche

{movie.vote_average}

- - + +
{ showModal ? : null}
diff --git a/src/SearchBar.jsx b/src/SearchBar.jsx index 5ce0b002..914aa3e9 100644 --- a/src/SearchBar.jsx +++ b/src/SearchBar.jsx @@ -8,7 +8,7 @@ const SearchBar = ({ searchQuery, searchHandler, sortMode, sortHandler, clearHan
setSearchText(e.target.value)} value={searchText} placeholder="Search..."> - { !(searchQuery === "") ? : null } + { !(searchQuery === "") ? : null }
setSearchText(e.target.value)} value={searchText} placeholder="Search..."> - { !(searchQuery === "") ? : null } + { !(searchQuery === "") ? : null }
setSearchText(e.target.value)} value={searchText} placeholder="Search..."> + { if (e.key === "Enter") {searchHandler(searchText)}}} onChange={(e) => setSearchText(e.target.value)} value={searchText} placeholder="Search..."> { !(searchQuery === "") ? : null }
From adfb372e7342c17eb135c7ad3bf06439e5304b67 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Wed, 11 Jun 2025 14:02:14 -0700 Subject: [PATCH 10/28] review1-branch-push --- src/MovieBox.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MovieBox.jsx b/src/MovieBox.jsx index ad0a096b..6f7832eb 100644 --- a/src/MovieBox.jsx +++ b/src/MovieBox.jsx @@ -3,7 +3,7 @@ import MovieList from './MovieList' import LoadMoreBar from './LoadMoreBar' import { useEffect, useState } from 'react' - +//review1-branch const MovieBox = (mode) => { const [movies, setMovies] = useState({}) @@ -49,7 +49,7 @@ const MovieBox = (mode) => { } } - const fetchMovies = async () => { + const fetchSearchMovies = async () => { try { const apiKey = import.meta.env.VITE_APP_API_KEY const options = { @@ -156,7 +156,7 @@ const MovieBox = (mode) => { useEffect(() => { if (mode.mode === "now-playing" && order.length === 0) { if (pageCleared) { - fetchMovies() + fetchSearchMovies() } setPageCleared(false) } else if (mode.mode === "favorites") { @@ -167,7 +167,7 @@ const MovieBox = (mode) => { }, [mode, pageCleared]) useEffect(() => { - fetchMovies(); + fetchSearchMovies(); }, [page, searchQuery]) useEffect(() => { From 2c927227bb55499b9ad4ecb3cb870e3c73e773a8 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Wed, 11 Jun 2025 16:28:44 -0700 Subject: [PATCH 11/28] Refactor movie fetching and processing functions - Rename fetchMovieList() to fetchAndProcessMoviesByIDList() for clarity - Split fetchAndProcessMoviesByIDList() into fetchMovieByID() and processMoviesByID() - Rename fetchSearchMovies() to fetchAndProcessMoviesBySearch() for clarity - Split fetchAndProcessMoviesBySearch into buildMovieSearchURL(), fetchMoviesBySearch(), and processMoviesBySearch() - Refactor sortMovies() to sortMovieOrder() for clarity and replace if/else with switch/case for readability - Update useEffect dependencies [mode, pageCleared] to use switch/case for readability These changes improve code readability and maintainability by clarifying function purposes and enhancing control flow readability. --- src/App.jsx | 1 + src/MovieBox.jsx | 250 ++++++++++++++++++++++++++------------------- src/MovieList.jsx | 1 - src/MovieModal.jsx | 3 +- 4 files changed, 147 insertions(+), 108 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 68904848..a34099f4 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -7,6 +7,7 @@ const App = () => { const [mode, setMode] = useState("now-playing") const [showSidebar, setShowSidebar] = useState(true) + // Sidebar links/mode change handler const handleModeChange = (newMode) => { setMode(newMode) } diff --git a/src/MovieBox.jsx b/src/MovieBox.jsx index 6f7832eb..98977163 100644 --- a/src/MovieBox.jsx +++ b/src/MovieBox.jsx @@ -6,6 +6,8 @@ import { useEffect, useState } from 'react' //review1-branch const MovieBox = (mode) => { + // Movies is a dict + // Decoupled movies and ids to allow O(1) movie retrieval const [movies, setMovies] = useState({}) const [originalOrder, setOriginalOrder] = useState([]) const [order, setOrder] = useState([]) @@ -17,128 +19,148 @@ const MovieBox = (mode) => { const [watched, setWatched] = useState([447273]) const [pageCleared, setPageCleared] = useState(false) - const fetchMovieList = async (movieIDList) => { - const fetchMovie = async (movieID) => { - const apiKey = import.meta.env.VITE_APP_API_KEY - const options = { - method: 'GET', - headers: { - accept: 'application/json', - Authorization: `Bearer ${apiKey}` - } - }; - let response = null - response = await fetch(`https://api.themoviedb.org/3/movie/${movieID}?language=en-US`, options) - if (!response.ok) { - throw new Error('Failed to fetch movies') + // Used for fetching and processing favorites and watched movies + // Takes in a list of movieIDs, fetches corresponding movies from TMDB + // Replaces the movies, order, and originalOrder states + const fetchAndProcessMovieByIDList = async (movieIDList) => { + const fetchMovieByID = async (movieID) => { + try { + const apiKey = import.meta.env.VITE_APP_API_KEY + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: `Bearer ${apiKey}` + } + }; + let response = null + response = await fetch(`https://api.themoviedb.org/3/movie/${movieID}?language=en-US`, options) + if (!response.ok) {throw new Error('Failed to fetch movie by ID')} + const data = await response.json() + return data + } catch (error) { + console.error(error) } - const data = await response.json() - return data } - const promises = movieIDList.map((movieID) => fetchMovie(movieID)) - let movieList = await Promise.all(promises) - let movieDict = {} - movieList.map((movie) => movieDict[movie.id] = movie) - setMovies(movieDict) - if (mode.mode === "favorites") { - setOriginalOrder(favorites) - setOrder(favorites) - } else if (mode.mode === "watched") { - setOriginalOrder(watched) - setOrder(watched) + const processMoviesByID = (movieData) => { + let movieDict = {} + movieData.map((movie) => movieDict[movie.id] = movie) + setMovies(movieDict) + switch (mode.mode) { + case "favorites": + setOriginalOrder(favorites) + setOrder(favorites) + break + case "watched": + setOriginalOrder(watched) + setOrder(watched) + break + } } + const promises = movieIDList.map((movieID) => fetchMovieByID(movieID)) + let movieData = await Promise.all(promises) + processMoviesByID(movieData) } - const fetchSearchMovies = async () => { - try { - const apiKey = import.meta.env.VITE_APP_API_KEY - const options = { - method: 'GET', - headers: { - accept: 'application/json', - Authorization: `Bearer ${apiKey}` - } - }; - let response = null - if (searchQuery === "") { - response = await fetch(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${page}&include_adult=false`, options) - } else { - response = await fetch(`https://api.themoviedb.org/3/search/movie?query=${searchQuery}&include_adult=false&language=en-US&page=${page}`, options) - } - if (!response.ok) { - throw new Error('Failed to fetch movies') - } - const data = await response.json() - if (data.total_pages === page) { - setMorePages(false) + // Used for fetching and processing now-playing and searched movies + // Takes in the page (and searchQuery) states to fetch relevant movies from TMDB + // Replaces the movies, order, and originalOrder states + const fetchAndProcessMovieBySearch = async () => { + const buildMovieSearchURL = () => { + return searchQuery === "" + ? `https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${page}&include_adult=false` + :`https://api.themoviedb.org/3/search/movie?query=${searchQuery}&include_adult=false&language=en-US&page=${page}` + } + const fetchMoviesBySearch = async () => { + try { + const apiKey = import.meta.env.VITE_APP_API_KEY + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: `Bearer ${apiKey}` + } + }; + const searchURL = buildMovieSearchURL() + let response = await fetch(searchURL, options) + if (!response.ok) {throw new Error('Failed to fetch movies by search')} + const data = await response.json() + return data + } catch (error) { + console.error(error) } + } + const processMoviesBySearch = (moviesData) => { + if (moviesData.total_pages === page) {setMorePages(false)} let newMovies = {} let newOrder = [] // TMDB seems to be giving 1-2 repeats from the end of the last page on load more - data.results.map( (movie) => { + moviesData.results.map( (movie) => { if (!order.includes(movie.id) && !newOrder.includes(movie.id)) { newMovies[movie.id] = movie newOrder.push(movie.id) } }) - setOriginalOrder([...originalOrder, ...newOrder]) newMovies = {...movies, ...newMovies} - setMovies(newMovies) newOrder = [...order, ...newOrder] + setOriginalOrder([...originalOrder, ...newOrder]) setOrder(newOrder) - } catch (error) { - console.error(error) + setMovies(newMovies) } + + let moviesData = await fetchMoviesBySearch() + processMoviesBySearch(moviesData) } - const sortMovies = () => { - // Was creating reference to movies, not a copy, so it wasn't re-rendering - // Destructuring makes a copy, will recognize sortedMovies as new - // So setMovies(sortedMovies) triggers re-render + // Sorts movies based on sort mode (either title, release date, or vote average + // Works on the movies state dict and the order state list + const sortMovieOrder = () => { if (movies.length === 0) { return; } // Split movies dict into keys (id) and values (movie) let movieEntries = Object.entries(movies) - if (sortMode === "title") { - movieEntries.sort((left_entry, right_entry) => { - const left = left_entry[1] - const right = right_entry[1] - if (left.title < right.title) {return -1;} - if (left.title > right.title) {return 1;} - return 0; - }) - } else if (sortMode === "release") { - // Listened to feedback about shortening sorting comparator - // Note: can't subtract strings in JS - movieEntries.sort((left_entry, right_entry) => { - const left = left_entry[1] - const right = right_entry[1] - left.date = new Date(left.release_date) - right.date = new Date(right.release_date) - return right.date - left.date - }) - } else if (sortMode === "vote") { - movieEntries.sort((left_entry, right_entry) => { - const left = left_entry[1] - const right = right_entry[1] - return right.vote_average-left.vote_average - }) + // Listened to feedback about using switch/case for repeated if/else if or where it's easier to read + switch (sortMode) { + case "title": + movieEntries.sort((left_entry, right_entry) => { + const left = left_entry[1] + const right = right_entry[1] + if (left.title < right.title) {return -1;} + if (left.title > right.title) {return 1;} + return 0; + }) + break; + case "release": + // Listened to feedback about shortening sorting comparator + // Note: can't subtract strings in JS + movieEntries.sort((left_entry, right_entry) => { + const left = left_entry[1] + const right = right_entry[1] + left.date = new Date(left.release_date) + right.date = new Date(right.release_date) + return right.date - left.date + }) + break; + case "vote": + movieEntries.sort((left_entry, right_entry) => { + const left = left_entry[1] + const right = right_entry[1] + return right.vote_average-left.vote_average + }) + break; + case "none": + setOrder(originalOrder) + return; + default: + console.log("Sort mode not found") + break } - if (sortMode==="none") { - setOrder(originalOrder) - } else { const newOrder = movieEntries.map( (entry) => entry[0]) setOrder(newOrder) - } } - // useEffect(() => { - // if (mode.mode === "now-playing") { - // updateSearchQuery("") - // } - // }, [mode]) - + // If mode changes to now-playing, resets relevant states to prevent the favorites/watched movies from lingering useEffect(() => { if (mode.mode === "now-playing") { setOrder([]) @@ -151,35 +173,47 @@ const MovieBox = (mode) => { } }, [mode]) - // By waiting for order to trigger and then fetching movies it assures the order is cleared for swap to favorites/watched -> now-playing - // Also used a pageCleared state to only fetchMovies() once relevant variables are cleared + // Uses the mode change as a trigger to grab the relevant movies for new mode + // If mode changed to now-playing, needs setPageCleared(true) to load now-playing movies + // to prevent race conditions mentioned above useEffect(() => { - if (mode.mode === "now-playing" && order.length === 0) { - if (pageCleared) { - fetchSearchMovies() - } - setPageCleared(false) - } else if (mode.mode === "favorites") { - fetchMovieList(favorites) - } else if (mode.mode === "watched") { - fetchMovieList(watched) + switch (mode.mode) { + case "now-playing": + if(order.length === 0 && pageCleared) { + fetchAndProcessMovieBySearch() + setPageCleared(false) + } + break + case "favorites": + fetchAndProcessMovieByIDList(favorites) + break + case "watched": + fetchAndProcessMovieByIDList(watched) + break } }, [mode, pageCleared]) + // When Load More is pressed or searchQuery is updated useEffect(() => { - fetchSearchMovies(); + fetchAndProcessMovieBySearch(); }, [page, searchQuery]) + // When sortMode is updated or movies is udpated + // Assures sort after sortMode change AND when Load More adds more movies useEffect(() => { - sortMovies() + sortMovieOrder() }, [sortMode, movies]) + // Increments page + // (note if fetchAndProcessMovieBySearch() loads last page for searchQuery, Load More button dissapeers) const loadMore = () => { setPage( (oldPage) => { return oldPage + 1; }) } + // Handles new search term + // If search term changes, it clears old movies/orders and resets page const updateSearchQuery = (term) => { if (!(term === searchQuery)) { setSortMode("none") @@ -190,16 +224,19 @@ const MovieBox = (mode) => { setSearchQuery(term) } } - + + // Sort mode dropdown handler const updateSortMode = (mode) => { setSortMode(mode) } + // Clear button handler const handleClear = () => { updateSortMode("none") updateSearchQuery("") } + // Favorite button handler const toggleFavorite = (movieID) => { let newFavorites = [...favorites] if (newFavorites.includes(movieID)) { @@ -211,6 +248,7 @@ const MovieBox = (mode) => { setFavorites(newFavorites) } + // Watched button handler const toggleWatched = (movieID) => { let newWatched = [...watched] if (newWatched.includes(movieID)) { diff --git a/src/MovieList.jsx b/src/MovieList.jsx index 0b87a666..fb53fa23 100644 --- a/src/MovieList.jsx +++ b/src/MovieList.jsx @@ -2,7 +2,6 @@ import MovieCard from './MovieCard' import './MovieList.css' const MovieList = ( {movies, order, favorites, watched, toggleFavorite, toggleWatched} ) => { - // { mode === "now-playing" ? console.log(mode === "now-playing") : null } return (
{ diff --git a/src/MovieModal.jsx b/src/MovieModal.jsx index 5406c513..9fff6159 100644 --- a/src/MovieModal.jsx +++ b/src/MovieModal.jsx @@ -6,6 +6,7 @@ const MovieModal = ({movieID, closeModal}) => { const [trailers, setTrailers] = useState([]) const [currentTrailer, setCurrentTrailer] = useState(0) + // Fetches movie from movieID const fetchMovie = async (movieID) => { try { const apiKey = import.meta.env.VITE_APP_API_KEY @@ -28,6 +29,7 @@ const MovieModal = ({movieID, closeModal}) => { } } + // Fetches trailers from movieID const fetchTrailers = async (movieID) => { try { const apiKey = import.meta.env.VITE_APP_API_KEY @@ -56,7 +58,6 @@ const MovieModal = ({movieID, closeModal}) => { fetchTrailers(movieID) }, []) - // console.log(trailers[currentTrailer].key) return (
closeModal(e)}>
From ea2e47cdb2fed279e62cc6c87cd7688c41363c1e Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Wed, 11 Jun 2025 16:59:33 -0700 Subject: [PATCH 12/28] Fix bug where processMoviesBySearch() was duplicating previous movies in Load More --- src/MovieBox.jsx | 18 +++++++++--------- src/SearchBar.jsx | 14 ++++++++------ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/MovieBox.jsx b/src/MovieBox.jsx index 98977163..462fac8e 100644 --- a/src/MovieBox.jsx +++ b/src/MovieBox.jsx @@ -22,7 +22,7 @@ const MovieBox = (mode) => { // Used for fetching and processing favorites and watched movies // Takes in a list of movieIDs, fetches corresponding movies from TMDB // Replaces the movies, order, and originalOrder states - const fetchAndProcessMovieByIDList = async (movieIDList) => { + const fetchAndProcessMoviesByIDList = async (movieIDList) => { const fetchMovieByID = async (movieID) => { try { const apiKey = import.meta.env.VITE_APP_API_KEY @@ -65,7 +65,7 @@ const MovieBox = (mode) => { // Used for fetching and processing now-playing and searched movies // Takes in the page (and searchQuery) states to fetch relevant movies from TMDB // Replaces the movies, order, and originalOrder states - const fetchAndProcessMovieBySearch = async () => { + const fetchAndProcessMoviesBySearch = async () => { const buildMovieSearchURL = () => { return searchQuery === "" ? `https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${page}&include_adult=false` @@ -101,9 +101,9 @@ const MovieBox = (mode) => { newOrder.push(movie.id) } }) + setOriginalOrder([...originalOrder, ...newOrder]) newMovies = {...movies, ...newMovies} newOrder = [...order, ...newOrder] - setOriginalOrder([...originalOrder, ...newOrder]) setOrder(newOrder) setMovies(newMovies) } @@ -180,22 +180,22 @@ const MovieBox = (mode) => { switch (mode.mode) { case "now-playing": if(order.length === 0 && pageCleared) { - fetchAndProcessMovieBySearch() + fetchAndProcessMoviesBySearch() setPageCleared(false) } break case "favorites": - fetchAndProcessMovieByIDList(favorites) + fetchAndProcessMoviesByIDList(favorites) break case "watched": - fetchAndProcessMovieByIDList(watched) + fetchAndProcessMoviesByIDList(watched) break } }, [mode, pageCleared]) // When Load More is pressed or searchQuery is updated useEffect(() => { - fetchAndProcessMovieBySearch(); + fetchAndProcessMoviesBySearch(); }, [page, searchQuery]) // When sortMode is updated or movies is udpated @@ -205,7 +205,7 @@ const MovieBox = (mode) => { }, [sortMode, movies]) // Increments page - // (note if fetchAndProcessMovieBySearch() loads last page for searchQuery, Load More button dissapeers) + // (note if fetchAndProcessMoviesBySearch() loads last page for searchQuery, Load More button dissapeers) const loadMore = () => { setPage( (oldPage) => { return oldPage + 1; @@ -261,7 +261,7 @@ const MovieBox = (mode) => { return (
- { mode.mode === "now-playing" ? : null } + { mode.mode === "now-playing" && (!Object.values(movies).length == 0 || !morePages) ? : null }
diff --git a/src/SearchBar.jsx b/src/SearchBar.jsx index 2c1a6eda..890e59da 100644 --- a/src/SearchBar.jsx +++ b/src/SearchBar.jsx @@ -1,15 +1,17 @@ import './SearchBar.css' import { useState } from 'react' -const SearchBar = ({ searchQuery, searchHandler, sortMode, sortHandler, clearHandler }) => { +const SearchBar = ({ mode, searchQuery, searchHandler, sortMode, sortHandler, clearHandler }) => { const [searchText, setSearchText] = useState("") return (
-
- { if (e.key === "Enter") {searchHandler(searchText)}}} onChange={(e) => setSearchText(e.target.value)} value={searchText} placeholder="Search..."> - - { !(searchQuery === "") ? : null } -
+ { mode === "now-playing" ? +
+ { if (e.key === "Enter") {searchHandler(searchText)}}} onChange={(e) => setSearchText(e.target.value)} value={searchText} placeholder="Search..."> + + { !(searchQuery === "") ? : null } +
: null + }
{ if (e.key === "Enter") {searchHandler(searchText)}}} onChange={(e) => setSearchText(e.target.value)} value={searchText} placeholder="Search..."> + { if (e.key === "Enter") {searchHandler(searchText)}}} onChange={(e) => searchTextHandler(e.target.value)} value={searchText} placeholder="Search..."> - { !(searchQuery === "") ? : null } + { !(searchQuery === "") ? : null }
: null }
diff --git a/src/SideBar.css b/src/SideBar.css index 34ddcbb9..9d44d9f4 100644 --- a/src/SideBar.css +++ b/src/SideBar.css @@ -15,9 +15,12 @@ display: flex; flex-direction: column; gap: 30px; + color: #9290C3; + transition: 0.2s; } .sidebar-links > a:hover { cursor: pointer; text-decoration: underline; + scale: 105%; } \ No newline at end of file From 00dd7071ca7a7d587fb9db007f7bf385fcbfa1ec Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Thu, 12 Jun 2025 09:54:30 -0700 Subject: [PATCH 14/28] Fix - modal opening in movie card due to moviecard:hover {scale: 105%}, moved modal component to movielist component --- src/App.css | 5 +++-- src/MovieCard.css | 2 +- src/MovieCard.jsx | 12 ++---------- src/MovieList.jsx | 20 ++++++++++++++++++-- src/MovieModal.css | 4 +--- src/SideBar.css | 2 +- 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/App.css b/src/App.css index fc8dfa0d..17c3b226 100644 --- a/src/App.css +++ b/src/App.css @@ -1,9 +1,10 @@ +/* https://colorhunt.co/palette/070f2b1b1a55535c919290c3 */ + + :root { --lucida-font: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; } -/* https://colorhunt.co/palette/070f2b1b1a55535c919290c3 */ - .App { text-align: center; display: flex; diff --git a/src/MovieCard.css b/src/MovieCard.css index 4d246f47..6b327764 100644 --- a/src/MovieCard.css +++ b/src/MovieCard.css @@ -7,7 +7,7 @@ border-radius: 9px; background-color: #535C91; font-family: --lucida-font; - transition: 0.2s; + transition: all 0.2s; } .moviecard:hover { diff --git a/src/MovieCard.jsx b/src/MovieCard.jsx index b40cc254..84fbe73a 100644 --- a/src/MovieCard.jsx +++ b/src/MovieCard.jsx @@ -4,17 +4,10 @@ import MovieModal from './MovieModal' import { BsEyeSlash, BsEyeFill } from "react-icons/bs"; import { FaStar, FaRegStar } from "react-icons/fa"; -const MovieCard = ( {movie, favorites, watched, toggleFavorite, toggleWatched } ) => { - const [showModal, setShowModal] = useState(false) - - const closeModal = (e) => { - if (e.currentTarget === e.target) { - setShowModal(false) - } - } +const MovieCard = ( {movie, favorites, watched, toggleFavorite, toggleWatched, openModal } ) => { return (
-
setShowModal(true)}> +
openModal(movie.id)}> {`${movie.title}

{movie.title}

{movie.vote_average}

@@ -23,7 +16,6 @@ const MovieCard = ( {movie, favorites, watched, toggleFavorite, toggleWatched }
- { showModal ? : null}
) } diff --git a/src/MovieList.jsx b/src/MovieList.jsx index fb53fa23..f65135da 100644 --- a/src/MovieList.jsx +++ b/src/MovieList.jsx @@ -1,15 +1,31 @@ import MovieCard from './MovieCard' import './MovieList.css' +import MovieModal from './MovieModal' +import { useState } from 'react' const MovieList = ( {movies, order, favorites, watched, toggleFavorite, toggleWatched} ) => { + const [showModal, setShowModal] = useState(false) + const [modalMovieID, setModalMovieID] = useState(0) + + const closeModal = (e) => { + if (e.currentTarget === e.target) { + setShowModal(false) + } + } + const openModal = (id) => { + setModalMovieID(id) + setShowModal(true) + } + return (
- { + { movies.length === 0 ?

No movies found!

: movies && order && order.map( (id) => { - return ; + return }) } + { showModal ? : null}
) } diff --git a/src/MovieModal.css b/src/MovieModal.css index 61440c2c..dc5bf36a 100644 --- a/src/MovieModal.css +++ b/src/MovieModal.css @@ -12,7 +12,6 @@ .moviemodal-content { background-color: #888; margin: 100px auto; - /* padding: 20px; */ border: 1px solid #888; width: 75vw; max-width: 100%; @@ -36,8 +35,7 @@ object-fit: contain; width: 60%; height: 40%; - margin-top: 400px - /* top: 0; */ + margin-top: 400px; } .genre-header { diff --git a/src/SideBar.css b/src/SideBar.css index 9d44d9f4..69cdb7f9 100644 --- a/src/SideBar.css +++ b/src/SideBar.css @@ -8,7 +8,7 @@ .sidebar-image { object-fit: contain; width: 15vw; - height: 15vh; + height: 200px; } .sidebar-links { From f13776b9935e7b6840893c7464f942a39cfadcbb Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Thu, 12 Jun 2025 12:09:58 -0700 Subject: [PATCH 15/28] Search input, search button, clear button, sort dropdown - restyled --- Flixster_README | 129 ++++++++++++++++++++++++++++++++++++++++++++ src/App.css | 9 ++-- src/LoadMoreBar.css | 3 +- src/MovieBox.css | 2 +- src/MovieBox.jsx | 1 + src/MovieCard.css | 32 +++++++---- src/MovieList.css | 1 + src/SearchBar.css | 72 +++++++++++++++++++++++++ src/SearchBar.jsx | 4 +- src/SideBar.css | 4 +- src/index.css | 5 -- 11 files changed, 237 insertions(+), 25 deletions(-) create mode 100644 Flixster_README diff --git a/Flixster_README b/Flixster_README new file mode 100644 index 00000000..7684be32 --- /dev/null +++ b/Flixster_README @@ -0,0 +1,129 @@ +📝 `NOTE` Use this template to initialize the contents of a README.md file for your application. As you work on your assignment over the course of the week, update the required or stretch features lists to indicate which features you have completed by changing `[ ]` to `[x]`. (🚫 Remove this paragraph before submitting your assignment.) + +## Unit Assignment: Flixster + +Submitted by: **Jack McClure** + +Estimated time spent: **14** hours spent in total + +Deployed Application (**required**): [Flixster Deployed Site](https://flixster-s5ux.onrender.com/) + +### Application Features + +#### REQUIRED FEATURES + +- [ ] **Display Movies** + - [ ] Users can view a list of current movies from The Movie Database API in a grid view. + - [ ] Movie tiles should be reasonably sized (at least 6 playlists on your laptop when full screen; large enough that the playlist components detailed in the next feature are legible). + - [ ] For each movie displayed, users can see the movie's: + - [ ] Title + - [ ] Poster image + - [ ] Vote average + - [ ] Users can load more current movies by clicking a button which adds more movies to the grid without reloading the entire page. +- [ ] **Search Functionality** + - [ ] Users can use a search bar to search for movies by title. + - [ ] The search bar should include: + - [ ] Text input field + - [ ] Submit/Search button + - [ ] Clear button + - [ ] Movies with a title containing the search query in the text input field are displayed in a grid view when the user either: + - [ ] Presses the Enter key + - [ ] Clicks the Submit/Search button + - [ ] Users can click the Clear button. When clicked: + - [ ] Most recent search results are cleared from the text input field and the grid view and all current movies are displayed in a grid view +- [ ] **Design Features** + - [ ] Website implements all of the following accessibility features: + - [ ] Semantic HTML + - [ ] [Color contrast](https://webaim.org/resources/contrastchecker/) + - [ ] Alt text for images + - [ ] Website implements responsive web design. + - [ ] Uses CSS Flexbox or CSS Grid + - [ ] Movie tiles and images shrink/grow in response to window size + - [ ] Users can click on a movie tile to view more details about a movie in a pop-up modal. + - [ ] The pop-up window is centered in the screen and does not occupy the entire screen. + - [ ] The pop-up window has a shadow to show that it is a pop-up and appears floating on the screen. + - [ ] The backdrop of the pop-up appears darker or in a different shade than before. including: + - [ ] The pop-up displays additional details about the moving including: + - [ ] Runtime in minutes + - [ ] Backdrop poster + - [ ] Release date + - [ ] Genres + - [ ] An overview + - [ ] Users can use a drop-down menu to sort movies. + - [ ] Drop-down allows movies to be sorted by: + - [ ] Title (alphabetic, A-Z) + - [ ] Release date (chronologically, most recent to oldest) + - [ ] Vote average (descending, highest to lowest) + - [ ] When a sort option is clicked, movies display in a grid according to selected criterion. + - [ ] Website displays: + - [ ] Header section + - [ ] Banner section + - [ ] Search bar + - [ ] Movie grid + - [ ] Footer section + - [ ] **VIDEO WALKTHROUGH SPECIAL INSTRUCTIONS**: To ease the grading process, please use the [color contrast checker](https://webaim.org/resources/contrastchecker/) to demonstrate to the grading team that text and background colors on your website have appropriate contrast. The Contrast Ratio should be above 4.5:1 and should have a green box surrounding it. + - [ ] **Deployment** + - [ ] Website is deployed via Render. + - [ ] **VIDEO WALKTHROUGH SPECIAL INSTRUCTIONS**: For ease of grading, please use the deployed version of your website when creating your walkthrough. + +#### STRETCH FEATURES + + +- [ ] **Embedded Movie Trailers** + - [ ] Within the pop-up modal displaying a movie's details, the movie trailer is viewable. + - [ ] When the trailer is clicked, users can play the movie trailer. +- [ ] **Favorite Button** + - [ ] For each movie displayed, users can favorite the movie. + - [ ] There should be visual element (such as a heart icon) on each movie's tile to show whether or not the movie has been favorited. + - [ ] If the movie is not favorited: + - [ ] Clicking on the visual element should mark the movie as favorited + - [ ] There should be visual feedback (such as the heart turning a different color) to show that the movie has been favorited by the user. + - [ ] If the movie is already favorited: + - [ ] Clicking on the visual element should mark the movie as *not* favorited. + - [ ] There should be visual feedback (such as the heart turning a different color) to show that the movie has been unfavorited. +- [ ] **Watched Checkbox** + - [ ] For each movie displayed, users can mark the movie as watched. + - [ ] There should be visual element (such as an eye icon) on each movie's tile to show whether or not the movie has been watched. + - [ ] If the movie has not been watched: + - [ ] Clicking on the visual element should mark the movie as watched + - [ ] There should be visual feedback (such as the eye turning a different color) to show that the movie has been watched by the user. + - [ ] If the movie is already watched: + - [ ] Clicking on the visual element should mark the movie as *not* watched. + - [ ] There should be visual feedback (such as the eye turning a different color) to show that the movie has not been watched. +- [ ] **Sidebar** + - [ ] The website includes a side navigation bar. + - [ ] The sidebar has three pages: + - [ ] Home + - [ ] Favorites + - [ ] Watched + - [ ] The Home page displays all current movies in a grid view, the search bar, and the sort movies drop-down. + - [ ] The Favorites page displays all favorited movies in a grid view. + - [ ] The Watched page displays all watched movies in a grid view. + +### Walkthrough Video + +`TODO://` Add the embedded URL code to your animated app walkthrough below, `ADD_EMBEDDED_CODE_HERE`. Make sure the video or gif actually renders and animates when viewing this README. Ensure your walkthrough showcases the presence and/or functionality of all features you implemented above (check them off as you film!). Pay attention to any **VIDEO WALKTHROUGH SPECIAL INSTRUCTIONS** checkboxes listed above to ensure graders see the full functionality of your website! (🚫 Remove this paragraph after adding walkthrough video) + +`ADD_EMBEDDED_CODE_HERE` + +### Reflection + +* Did the topics discussed in your labs prepare you to complete the assignment? Be specific, which features in your weekly assignment did you feel unprepared to complete? + +Add your response here + +* If you had more time, what would you have done differently? Would you have added additional features? Changed the way your project responded to a particular event, etc. + +Add your response here + +* Reflect on your project demo, what went well? Were there things that maybe didn't go as planned? Did you notice something that your peer did that you would like to try next time? + +Add your response here + +### Open-source libraries used + +- Add any links to open-source libraries used in your project. + +### Shout out + +Give a shout out to somebody from your cohort that especially helped you during your project. This can be a fellow peer, instructor, TA, mentor, etc. diff --git a/src/App.css b/src/App.css index 17c3b226..eb15a544 100644 --- a/src/App.css +++ b/src/App.css @@ -10,12 +10,11 @@ display: flex; flex-direction: row; width: 100%; - height: 100vh; background-color: #070F2B; + min-height: 100vh; } .App-header { - background-color: #282c34; display: flex; flex-direction: row; align-items: center; @@ -62,19 +61,21 @@ header { .content { display: flex; flex-direction: column; - height: 100%; + width: 80%; } main { display: flex; flex-direction: row; + width: 100%; } footer { width: 100%; - background-color: #888; + background-color: #1B1A55; position: relative; bottom: 0; + color: white; } @media (max-width: 600px) { diff --git a/src/LoadMoreBar.css b/src/LoadMoreBar.css index cf9165a1..199f11e8 100644 --- a/src/LoadMoreBar.css +++ b/src/LoadMoreBar.css @@ -1,6 +1,5 @@ .loadmore-bar { width: 100%; - background-color: #888; height: 80px; display: flex; flex-direction: column; @@ -11,4 +10,6 @@ .loadmore-button { height: 40px; width: 160px; + background-color: #1B1A55; + border-radius: 9px; } \ No newline at end of file diff --git a/src/MovieBox.css b/src/MovieBox.css index 8e71d3b8..e557842f 100644 --- a/src/MovieBox.css +++ b/src/MovieBox.css @@ -1,4 +1,4 @@ .moviebox { display: flex; - flex-direction: row; + flex-direction: column; } \ No newline at end of file diff --git a/src/MovieBox.jsx b/src/MovieBox.jsx index 94bc3b9f..73480f72 100644 --- a/src/MovieBox.jsx +++ b/src/MovieBox.jsx @@ -1,6 +1,7 @@ import SearchBar from './SearchBar' import MovieList from './MovieList' import LoadMoreBar from './LoadMoreBar' +import './MovieBox.css' import { useEffect, useState } from 'react' //review1-branch diff --git a/src/MovieCard.css b/src/MovieCard.css index 6b327764..cc72dc01 100644 --- a/src/MovieCard.css +++ b/src/MovieCard.css @@ -3,7 +3,7 @@ flex-direction: column; width: 20%; flex-basis: 20%; - border: 2px grey solid; + border: 2px #070F2B solid; border-radius: 9px; background-color: #535C91; font-family: --lucida-font; @@ -15,31 +15,37 @@ } .moviecard-image { - border-top-left-radius: 9px; - border-top-right-radius: 9px; - margin-bottom: 5px; + border-top-left-radius: 6px; + border-top-right-radius: 6px; } .moviecard-modalclickable { display: flex; flex-direction: column; - height: 70%; + /* height: 70%; */ flex-grow: 1; - color: #9290C3; + /* color: #9290C3; */ + /* gap: 5%; */ + /* justify-content: center; */ } .moviecard-modalclickable > p { - margin: 8px; -} - -.moviecard-modalclickable:hover { - cursor: pointer; + margin: auto; + padding: 5px; } .moviecard-title { height: 30%; } +/* .moviecard-vote { + height: 5%; +} */ + +.moviecard-modalclickable:hover { + cursor: pointer; +} + .moviecard-buttons { position: relative; bottom: 3px; @@ -55,6 +61,10 @@ width: 30%; border-radius: 9px; transition: 0.2s; + display: flex; + justify-content: center; + align-items: center; + margin-top: 10px; } .moviecard-buttons > button:hover { diff --git a/src/MovieList.css b/src/MovieList.css index 4a79745e..18b3a634 100644 --- a/src/MovieList.css +++ b/src/MovieList.css @@ -9,4 +9,5 @@ border-radius: 9px; background-color: #1B1A55; padding-bottom: 30px; + margin-bottom: 0; } \ No newline at end of file diff --git a/src/SearchBar.css b/src/SearchBar.css index e69de29b..a5d2a7ca 100644 --- a/src/SearchBar.css +++ b/src/SearchBar.css @@ -0,0 +1,72 @@ +.searchbar { + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; +} + +.searchbar-searchbar { + display: flex; + flex-direction: row; + gap: 5%; + background-color: #1B1A55; + width: 40%; + justify-self: center; + justify-content: center; + align-items: center; + border-radius: 9px; + height: 40px; +} + +.searchbar-input { + height: 20px; + width: 50%; + border-radius: 9px; + margin-left: 5px; +} + + +.searchbar-submit { + background-color: #1B1A55; + border-radius: 8px; + height: 30px; + padding-bottom: 3px; + border: 2px white solid; + transition: 0.2s; +} + +.searchbar-submit:hover { + scale: 105%; +} + +.searchbar-clear { + background-color: #1B1A55; + border-radius: 8px; + height: 30px; + padding-bottom: 3px; + border: 2px white solid; + transition: 0.2s; +} + +.searchbar-clear:hover { + scale: 105%; +} + + +.searchbar-sort { + display: flex; + flex-direction: row; + background-color: #1B1A55; + width: 20%; + justify-content: center; + border-radius: 9px; + height: 35px; + align-items: center; +} + +.searchbar-sort-dropdown { + height: 25px; + width: 90%; + border: 3px solid #1B1A55; + border-radius: 9px; +} \ No newline at end of file diff --git a/src/SearchBar.jsx b/src/SearchBar.jsx index 3c226e3b..6c89ef06 100644 --- a/src/SearchBar.jsx +++ b/src/SearchBar.jsx @@ -6,13 +6,13 @@ const SearchBar = ({ mode, searchQuery, searchText, searchTextHandler, searchHan
{ mode === "now-playing" ?
- { if (e.key === "Enter") {searchHandler(searchText)}}} onChange={(e) => searchTextHandler(e.target.value)} value={searchText} placeholder="Search..."> + { if (e.key === "Enter") {searchHandler(searchText)}}} onChange={(e) => searchTextHandler(e.target.value)} value={searchText} placeholder="Search for movies..."> { !(searchQuery === "") ? : null }
: null }
- {sortHandler(e.target.value);}}> diff --git a/src/SideBar.css b/src/SideBar.css index 69cdb7f9..1e64eb67 100644 --- a/src/SideBar.css +++ b/src/SideBar.css @@ -2,13 +2,15 @@ display: flex; flex-direction: column; gap: 40px; - width: 30%; + width: 20%; + margin-top: 30px; } .sidebar-image { object-fit: contain; width: 15vw; height: 200px; + align-self: center; } .sidebar-links { diff --git a/src/index.css b/src/index.css index 81744e63..20255f5a 100644 --- a/src/index.css +++ b/src/index.css @@ -11,9 +11,4 @@ button { font-size: 16px; font-weight: bold; transition: background-color 0.3s ease; -} - -button:hover { - background-color: #777; - color: white; } \ No newline at end of file From 09df52c3735fcd8592aa41fa647610b0a21f495f Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Thu, 12 Jun 2025 12:33:02 -0700 Subject: [PATCH 16/28] fix-footer at bottom of content --- src/App.css | 1 + src/MovieBox.css | 2 ++ src/MovieModal.css | 31 ++++++++++++++++++++++++++++++- src/SearchBar.css | 3 +++ src/SideBar.css | 1 + 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/App.css b/src/App.css index eb15a544..6e58d646 100644 --- a/src/App.css +++ b/src/App.css @@ -76,6 +76,7 @@ footer { position: relative; bottom: 0; color: white; + margin-top: auto; } @media (max-width: 600px) { diff --git a/src/MovieBox.css b/src/MovieBox.css index e557842f..50006f13 100644 --- a/src/MovieBox.css +++ b/src/MovieBox.css @@ -1,4 +1,6 @@ .moviebox { display: flex; flex-direction: column; + width: 100%; + min-height: 100%; } \ No newline at end of file diff --git a/src/MovieModal.css b/src/MovieModal.css index dc5bf36a..8c2990db 100644 --- a/src/MovieModal.css +++ b/src/MovieModal.css @@ -10,7 +10,7 @@ } .moviemodal-content { - background-color: #888; + background-color: #535C91; margin: 100px auto; border: 1px solid #888; width: 75vw; @@ -23,12 +23,28 @@ justify-content: center; align-items: center; overflow: auto; + font-family: --lucida; +} + +.moviemodal-content > * { + width: 100%; } .moviemodal-close-button { position: absolute; right: 0; top: 0; + width: 30px; + border: none; + margin-top: 10px; + margin-right: 10px; + border-radius: 9px; + background-color: red; + transition: 0.2s; +} + +.moviemodal-close-button:hover { + scale: 125%; } .moviemodal-content-image { @@ -70,6 +86,19 @@ iframe { padding-bottom: 15px; } +.trailer-select > button { + border: none; + background-color: #070F2B; + transition: 0.2s; +} + +.trailer-select > button:hover { + border: none; + border-radius: 9px; + background-color: #9290C3; +} + + .moviemodal-content-info { width: 80% } \ No newline at end of file diff --git a/src/SearchBar.css b/src/SearchBar.css index a5d2a7ca..0897da6a 100644 --- a/src/SearchBar.css +++ b/src/SearchBar.css @@ -16,6 +16,7 @@ align-items: center; border-radius: 9px; height: 40px; + min-width: 400px; } .searchbar-input { @@ -46,6 +47,7 @@ padding-bottom: 3px; border: 2px white solid; transition: 0.2s; + margin-right: 8px; } .searchbar-clear:hover { @@ -62,6 +64,7 @@ border-radius: 9px; height: 35px; align-items: center; + min-width: 220px; } .searchbar-sort-dropdown { diff --git a/src/SideBar.css b/src/SideBar.css index 1e64eb67..a4e14161 100644 --- a/src/SideBar.css +++ b/src/SideBar.css @@ -4,6 +4,7 @@ gap: 40px; width: 20%; margin-top: 30px; + gap: 50px; } .sidebar-image { From 2c2a35cdc9a8787a88de5f3ad48d016e576b71e4 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Thu, 12 Jun 2025 13:00:23 -0700 Subject: [PATCH 17/28] CSS variable styling - colors controlled by variables in App.css --- src/App.css | 11 ++++++++--- src/LoadMoreBar.css | 8 +++++++- src/MovieCard.css | 16 +++++++++------- src/MovieList.css | 2 +- src/MovieModal.css | 12 +++++++----- src/SearchBar.css | 15 ++++++++------- src/SideBar.css | 2 +- 7 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/App.css b/src/App.css index 6e58d646..fb947f99 100644 --- a/src/App.css +++ b/src/App.css @@ -3,6 +3,11 @@ :root { --lucida-font: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; + --steelgray: #1e1e2f; + --martinique: #2a2a4c; + --butterflybush: #505091; + --wildblueyonder: #8080bc; + --gallery: #f0f0f0; } .App { @@ -10,7 +15,7 @@ display: flex; flex-direction: row; width: 100%; - background-color: #070F2B; + background-color: var(--steelgray); min-height: 100vh; } @@ -37,7 +42,7 @@ header { flex-direction: column; align-items: center; width: 100%; - color: #9290C3; + color: var(--gallery); } .flixster-title { @@ -75,7 +80,7 @@ footer { background-color: #1B1A55; position: relative; bottom: 0; - color: white; + color: var(--gallery); margin-top: auto; } diff --git a/src/LoadMoreBar.css b/src/LoadMoreBar.css index 199f11e8..b64ce823 100644 --- a/src/LoadMoreBar.css +++ b/src/LoadMoreBar.css @@ -10,6 +10,12 @@ .loadmore-button { height: 40px; width: 160px; - background-color: #1B1A55; + color: var(--gallery); + background-color: var(--butterflybush); border-radius: 9px; +} + +.loadmore-button:hover { + background-color: var(--gallery); + color: var(--butterflybush); } \ No newline at end of file diff --git a/src/MovieCard.css b/src/MovieCard.css index cc72dc01..f5701069 100644 --- a/src/MovieCard.css +++ b/src/MovieCard.css @@ -1,13 +1,15 @@ .moviecard { display: flex; flex-direction: column; - width: 20%; - flex-basis: 20%; - border: 2px #070F2B solid; + width: 12%; + flex-basis: 12%; + border: 2px var(--steelgray) solid; border-radius: 9px; - background-color: #535C91; + background-color: var(--martinique); + color: var(--gallery); font-family: --lucida-font; transition: all 0.2s; + margin: 1px; } .moviecard:hover { @@ -57,7 +59,7 @@ } .moviecard-buttons > button { - background-color: #535C91; + background-color: var(--butterflybush); width: 30%; border-radius: 9px; transition: 0.2s; @@ -69,9 +71,9 @@ .moviecard-buttons > button:hover { scale: 105%; - background-color: #9290C3; + background-color: var(--wildblueyonder); } .movielist { - background-color: #535C91; + background-color: var(--butterflybush); } \ No newline at end of file diff --git a/src/MovieList.css b/src/MovieList.css index 18b3a634..69db7edb 100644 --- a/src/MovieList.css +++ b/src/MovieList.css @@ -7,7 +7,7 @@ justify-content: center; padding-top: 30px; border-radius: 9px; - background-color: #1B1A55; + background-color: var(--butterflybush); padding-bottom: 30px; margin-bottom: 0; } \ No newline at end of file diff --git a/src/MovieModal.css b/src/MovieModal.css index 8c2990db..7ca397f2 100644 --- a/src/MovieModal.css +++ b/src/MovieModal.css @@ -10,9 +10,9 @@ } .moviemodal-content { - background-color: #535C91; + background-color: var(--martinique); margin: 100px auto; - border: 1px solid #888; + border: 1px solid var(--martinique); width: 75vw; max-width: 100%; height: 80%; @@ -24,6 +24,7 @@ align-items: center; overflow: auto; font-family: --lucida; + color: var(--gallery) } .moviemodal-content > * { @@ -39,7 +40,8 @@ margin-top: 10px; margin-right: 10px; border-radius: 9px; - background-color: red; + background-color: var(--wildblueyonder); + color: var(--gallery); transition: 0.2s; } @@ -88,14 +90,14 @@ iframe { .trailer-select > button { border: none; - background-color: #070F2B; + background-color: var(--steelgray); transition: 0.2s; } .trailer-select > button:hover { border: none; border-radius: 9px; - background-color: #9290C3; + background-color: var(--wildblueyonder); } diff --git a/src/SearchBar.css b/src/SearchBar.css index 0897da6a..12c63707 100644 --- a/src/SearchBar.css +++ b/src/SearchBar.css @@ -9,7 +9,7 @@ display: flex; flex-direction: row; gap: 5%; - background-color: #1B1A55; + background-color: var(--martinique); width: 40%; justify-self: center; justify-content: center; @@ -24,15 +24,16 @@ width: 50%; border-radius: 9px; margin-left: 5px; + border: 3px solid var(--steelgray); } .searchbar-submit { - background-color: #1B1A55; + background-color: var(--butterflybush); border-radius: 8px; height: 30px; padding-bottom: 3px; - border: 2px white solid; + border: 2px var(--gallery) solid; transition: 0.2s; } @@ -41,11 +42,11 @@ } .searchbar-clear { - background-color: #1B1A55; + background-color: var(--butterflybush); border-radius: 8px; height: 30px; padding-bottom: 3px; - border: 2px white solid; + border: 2px var(--gallery) solid; transition: 0.2s; margin-right: 8px; } @@ -58,7 +59,7 @@ .searchbar-sort { display: flex; flex-direction: row; - background-color: #1B1A55; + background-color: var(--martinique); width: 20%; justify-content: center; border-radius: 9px; @@ -70,6 +71,6 @@ .searchbar-sort-dropdown { height: 25px; width: 90%; - border: 3px solid #1B1A55; + border: 3px solid var(--steelgray); border-radius: 9px; } \ No newline at end of file diff --git a/src/SideBar.css b/src/SideBar.css index a4e14161..61d90a75 100644 --- a/src/SideBar.css +++ b/src/SideBar.css @@ -18,7 +18,7 @@ display: flex; flex-direction: column; gap: 30px; - color: #9290C3; + color: var(--gallery); transition: 0.2s; } From ab5a83b82dd5290ac22b3cdac1a89bae1059e6e3 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Thu, 12 Jun 2025 14:27:01 -0700 Subject: [PATCH 18/28] Fix - load more was showing even when showing last page of results --- src/MovieBox.css | 5 +++++ src/MovieBox.jsx | 4 ++-- src/SearchBar.jsx | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/MovieBox.css b/src/MovieBox.css index 50006f13..40e52fa2 100644 --- a/src/MovieBox.css +++ b/src/MovieBox.css @@ -3,4 +3,9 @@ flex-direction: column; width: 100%; min-height: 100%; +} + +.no-more-movies { + font-size: 30px; + color: var(--gallery) } \ No newline at end of file diff --git a/src/MovieBox.jsx b/src/MovieBox.jsx index 73480f72..7556fdc0 100644 --- a/src/MovieBox.jsx +++ b/src/MovieBox.jsx @@ -93,6 +93,7 @@ const MovieBox = (mode) => { } } const processMoviesBySearch = (moviesData) => { + console.log(moviesData) if (moviesData.total_pages === page) {setMorePages(false)} let newMovies = {} let newOrder = [] @@ -267,12 +268,11 @@ const MovieBox = (mode) => { } setWatched(newWatched) } - return (
- { mode.mode === "now-playing" && (!Object.values(movies).length == 0 || !morePages) ? : null } + { mode.mode === "now-playing" && !(Object.values(movies).length == 0) && morePages ? :

End of results...

}
) } diff --git a/src/SearchBar.jsx b/src/SearchBar.jsx index 6c89ef06..c1cabbe6 100644 --- a/src/SearchBar.jsx +++ b/src/SearchBar.jsx @@ -8,7 +8,8 @@ const SearchBar = ({ mode, searchQuery, searchText, searchTextHandler, searchHan
{ if (e.key === "Enter") {searchHandler(searchText)}}} onChange={(e) => searchTextHandler(e.target.value)} value={searchText} placeholder="Search for movies..."> - { !(searchQuery === "") ? : null } + {/* { !(searchQuery === "") ? : null } */} +
: null }
From d904ffed5d7b36509822f809b92e336c5f8ad7b6 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Thu, 12 Jun 2025 15:52:07 -0700 Subject: [PATCH 19/28] Fix - condensed sort functions for readability fix - removed testing console logs fix - renamed promises to moviePromises for readability --- src/MovieBox.jsx | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/MovieBox.jsx b/src/MovieBox.jsx index 7556fdc0..ef62b5b3 100644 --- a/src/MovieBox.jsx +++ b/src/MovieBox.jsx @@ -59,8 +59,8 @@ const MovieBox = (mode) => { break } } - const promises = movieIDList.map((movieID) => fetchMovieByID(movieID)) - let movieData = await Promise.all(promises) + const moviePromises = movieIDList.map((movieID) => fetchMovieByID(movieID)) + let movieData = await Promise.all(moviePromises) processMoviesByID(movieData) } @@ -93,7 +93,6 @@ const MovieBox = (mode) => { } } const processMoviesBySearch = (moviesData) => { - console.log(moviesData) if (moviesData.total_pages === page) {setMorePages(false)} let newMovies = {} let newOrder = [] @@ -127,10 +126,8 @@ const MovieBox = (mode) => { switch (sortMode) { case "title": movieEntries.sort((left_entry, right_entry) => { - const left = left_entry[1] - const right = right_entry[1] - if (left.title < right.title) {return -1;} - if (left.title > right.title) {return 1;} + if (left_entry[1].title < right_entry[1].title) {return -1;} + if (left_entry[1].title > right_entry[1].title) {return 1;} return 0; }) break; @@ -138,25 +135,21 @@ const MovieBox = (mode) => { // Listened to feedback about shortening sorting comparator // Note: can't subtract strings in JS movieEntries.sort((left_entry, right_entry) => { - const left = left_entry[1] - const right = right_entry[1] - left.date = new Date(left.release_date) - right.date = new Date(right.release_date) - return right.date - left.date + const left_date = new Date(left_entry[1].release_date) + const right_date = new Date(right_entry[1].release_date) + return right_date - left_date }) break; case "vote": movieEntries.sort((left_entry, right_entry) => { - const left = left_entry[1] - const right = right_entry[1] - return right.vote_average-left.vote_average + return right_entry[1].vote_average-left_entry[1].vote_average }) break; case "none": setOrder(originalOrder) return; default: - console.log("Sort mode not found") + console.error("Sort mode not found") break } const newOrder = movieEntries.map( (entry) => entry[0]) @@ -251,7 +244,6 @@ const MovieBox = (mode) => { let newFavorites = [...favorites] if (newFavorites.includes(movieID)) { newFavorites = newFavorites.filter( (id) => {return id !== movieID}) - console.log(movieID) } else { newFavorites.push(movieID) } From 80232797cd8c15d985e5f7637c9d238716e3de99 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Thu, 12 Jun 2025 18:03:37 -0700 Subject: [PATCH 20/28] Fixed minimum movie card width to 90px --- src/MovieCard.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MovieCard.css b/src/MovieCard.css index f5701069..4e5182e0 100644 --- a/src/MovieCard.css +++ b/src/MovieCard.css @@ -10,6 +10,7 @@ font-family: --lucida-font; transition: all 0.2s; margin: 1px; + min-width: 90px; } .moviecard:hover { From 89bbc36217db98e3d1adca5c724f7d7b1510c41a Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Thu, 12 Jun 2025 18:13:20 -0700 Subject: [PATCH 21/28] Changed now-playing to home on sidebar --- src/SideBar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SideBar.jsx b/src/SideBar.jsx index 0e1f5a94..33afeca4 100644 --- a/src/SideBar.jsx +++ b/src/SideBar.jsx @@ -3,7 +3,7 @@ import './SideBar.css' const SideBarLinks = ({modeHandler}) => { return ( From 32cd60921fc5d5aaee31cf55b57f55f838054c12 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Fri, 13 Jun 2025 09:52:48 -0700 Subject: [PATCH 22/28] Fix - increased contrast of searchbar placeholder for accessibility --- src/SearchBar.css | 5 +++++ src/SideBar.css | 1 + 2 files changed, 6 insertions(+) diff --git a/src/SearchBar.css b/src/SearchBar.css index 12c63707..e98611f7 100644 --- a/src/SearchBar.css +++ b/src/SearchBar.css @@ -19,6 +19,11 @@ min-width: 400px; } +input::placeholder { + color: black; + opacity: 0.8 +} + .searchbar-input { height: 20px; width: 50%; diff --git a/src/SideBar.css b/src/SideBar.css index 61d90a75..c37f04f8 100644 --- a/src/SideBar.css +++ b/src/SideBar.css @@ -12,6 +12,7 @@ width: 15vw; height: 200px; align-self: center; + margin-left: 30px; } .sidebar-links { From 8347d9d3373196cbf8eb9a5c65a8b88e87910f20 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Fri, 13 Jun 2025 09:57:33 -0700 Subject: [PATCH 23/28] Fix - added border to banner for distinct banner --- src/App.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/App.css b/src/App.css index fb947f99..b6a6e2de 100644 --- a/src/App.css +++ b/src/App.css @@ -52,6 +52,8 @@ header { .banner { font-size: 30px; + border: 2px var(--gallery) solid; + width: 80%; } .header-image { From 89b0dfd29d0d5d549ec3eb42e6c9300c28512a8d Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Fri, 13 Jun 2025 10:55:03 -0700 Subject: [PATCH 24/28] Completed Flixster_README for submission --- Flixster_README | 164 ++++++++++++++++++++++++------------------------ 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/Flixster_README b/Flixster_README index 7684be32..66a1cfac 100644 --- a/Flixster_README +++ b/Flixster_README @@ -12,99 +12,99 @@ Deployed Application (**required**): [Flixster Deployed Site](https://flixster-s #### REQUIRED FEATURES -- [ ] **Display Movies** - - [ ] Users can view a list of current movies from The Movie Database API in a grid view. - - [ ] Movie tiles should be reasonably sized (at least 6 playlists on your laptop when full screen; large enough that the playlist components detailed in the next feature are legible). - - [ ] For each movie displayed, users can see the movie's: - - [ ] Title - - [ ] Poster image - - [ ] Vote average - - [ ] Users can load more current movies by clicking a button which adds more movies to the grid without reloading the entire page. -- [ ] **Search Functionality** - - [ ] Users can use a search bar to search for movies by title. +- [X] **Display Movies** + - [X] Users can view a list of current movies from The Movie Database API in a grid view. + - [X] Movie tiles should be reasonably sized (at least 6 playlists on your laptop when full screen; large enough that the playlist components detailed in the next feature are legible). + - [X] For each movie displayed, users can see the movie's: + - [X] Title + - [X] Poster image + - [X] Vote average + - [X] Users can load more current movies by clicking a button which adds more movies to the grid without reloading the entire page. +- [X] **Search Functionality** + - [X] Users can use a search bar to search for movies by title. - [ ] The search bar should include: - - [ ] Text input field - - [ ] Submit/Search button - - [ ] Clear button - - [ ] Movies with a title containing the search query in the text input field are displayed in a grid view when the user either: - - [ ] Presses the Enter key - - [ ] Clicks the Submit/Search button - - [ ] Users can click the Clear button. When clicked: - - [ ] Most recent search results are cleared from the text input field and the grid view and all current movies are displayed in a grid view -- [ ] **Design Features** - - [ ] Website implements all of the following accessibility features: - - [ ] Semantic HTML - - [ ] [Color contrast](https://webaim.org/resources/contrastchecker/) - - [ ] Alt text for images - - [ ] Website implements responsive web design. - - [ ] Uses CSS Flexbox or CSS Grid - - [ ] Movie tiles and images shrink/grow in response to window size - - [ ] Users can click on a movie tile to view more details about a movie in a pop-up modal. - - [ ] The pop-up window is centered in the screen and does not occupy the entire screen. - - [ ] The pop-up window has a shadow to show that it is a pop-up and appears floating on the screen. - - [ ] The backdrop of the pop-up appears darker or in a different shade than before. including: - - [ ] The pop-up displays additional details about the moving including: - - [ ] Runtime in minutes - - [ ] Backdrop poster - - [ ] Release date - - [ ] Genres - - [ ] An overview - - [ ] Users can use a drop-down menu to sort movies. - - [ ] Drop-down allows movies to be sorted by: - - [ ] Title (alphabetic, A-Z) - - [ ] Release date (chronologically, most recent to oldest) - - [ ] Vote average (descending, highest to lowest) - - [ ] When a sort option is clicked, movies display in a grid according to selected criterion. - - [ ] Website displays: - - [ ] Header section - - [ ] Banner section - - [ ] Search bar - - [ ] Movie grid - - [ ] Footer section - - [ ] **VIDEO WALKTHROUGH SPECIAL INSTRUCTIONS**: To ease the grading process, please use the [color contrast checker](https://webaim.org/resources/contrastchecker/) to demonstrate to the grading team that text and background colors on your website have appropriate contrast. The Contrast Ratio should be above 4.5:1 and should have a green box surrounding it. - - [ ] **Deployment** - - [ ] Website is deployed via Render. - - [ ] **VIDEO WALKTHROUGH SPECIAL INSTRUCTIONS**: For ease of grading, please use the deployed version of your website when creating your walkthrough. + - [X] Text input field + - [X] Submit/Search button + - [X] Clear button + - [X] Movies with a title containing the search query in the text input field are displayed in a grid view when the user either: + - [X] Presses the Enter key + - [X] Clicks the Submit/Search button + - [X] Users can click the Clear button. When clicked: + - [X] Most recent search results are cleared from the text input field and the grid view and all current movies are displayed in a grid view +- [X] **Design Features** + - [X] Website implements all of the following accessibility features: + - [X] Semantic HTML + - [X] [Color contrast](https://webaim.org/resources/contrastchecker/) + - [X] Alt text for images + - [X] Website implements responsive web design. + - [X] Uses CSS Flexbox or CSS Grid + - [X] Movie tiles and images shrink/grow in response to window size + - [X] Users can click on a movie tile to view more details about a movie in a pop-up modal. + - [X] The pop-up window is centered in the screen and does not occupy the entire screen. + - [X] The pop-up window has a shadow to show that it is a pop-up and appears floating on the screen. + - [X] The backdrop of the pop-up appears darker or in a different shade than before. including: + - [X] The pop-up displays additional details about the moving including: + - [X] Runtime in minutes + - [X] Backdrop poster + - [X] Release date + - [X] Genres + - [X] An overview + - [X] Users can use a drop-down menu to sort movies. + - [X] Drop-down allows movies to be sorted by: + - [X] Title (alphabetic, A-Z) + - [X] Release date (chronologically, most recent to oldest) + - [X] Vote average (descending, highest to lowest) + - [X] When a sort option is clicked, movies display in a grid according to selected criterion. + - [X] Website displays: + - [X] Header section + - [X] Banner section + - [X] Search bar + - [X] Movie grid + - [X] Footer section + - [X] **VIDEO WALKTHROUGH SPECIAL INSTRUCTIONS**: To ease the grading process, please use the [color contrast checker](https://webaim.org/resources/contrastchecker/) to demonstrate to the grading team that text and background colors on your website have appropriate contrast. The Contrast Ratio should be above 4.5:1 and should have a green box surrounding it. + - [X] **Deployment** + - [X] Website is deployed via Render. + - [X] **VIDEO WALKTHROUGH SPECIAL INSTRUCTIONS**: For ease of grading, please use the deployed version of your website when creating your walkthrough. #### STRETCH FEATURES -- [ ] **Embedded Movie Trailers** - - [ ] Within the pop-up modal displaying a movie's details, the movie trailer is viewable. - - [ ] When the trailer is clicked, users can play the movie trailer. -- [ ] **Favorite Button** - - [ ] For each movie displayed, users can favorite the movie. - - [ ] There should be visual element (such as a heart icon) on each movie's tile to show whether or not the movie has been favorited. - - [ ] If the movie is not favorited: - - [ ] Clicking on the visual element should mark the movie as favorited - - [ ] There should be visual feedback (such as the heart turning a different color) to show that the movie has been favorited by the user. - - [ ] If the movie is already favorited: - - [ ] Clicking on the visual element should mark the movie as *not* favorited. - - [ ] There should be visual feedback (such as the heart turning a different color) to show that the movie has been unfavorited. -- [ ] **Watched Checkbox** - - [ ] For each movie displayed, users can mark the movie as watched. - - [ ] There should be visual element (such as an eye icon) on each movie's tile to show whether or not the movie has been watched. - - [ ] If the movie has not been watched: - - [ ] Clicking on the visual element should mark the movie as watched - - [ ] There should be visual feedback (such as the eye turning a different color) to show that the movie has been watched by the user. - - [ ] If the movie is already watched: - - [ ] Clicking on the visual element should mark the movie as *not* watched. - - [ ] There should be visual feedback (such as the eye turning a different color) to show that the movie has not been watched. +- [X] **Embedded Movie Trailers** + - [X] Within the pop-up modal displaying a movie's details, the movie trailer is viewable. + - [X] When the trailer is clicked, users can play the movie trailer. +- [X] **Favorite Button** + - [X] For each movie displayed, users can favorite the movie. + - [X] There should be visual element (such as a heart icon) on each movie's tile to show whether or not the movie has been favorited. + - [X] If the movie is not favorited: + - [X] Clicking on the visual element should mark the movie as favorited + - [X] There should be visual feedback (such as the heart turning a different color) to show that the movie has been favorited by the user. + - [X] If the movie is already favorited: + - [X] Clicking on the visual element should mark the movie as *not* favorited. + - [X] There should be visual feedback (such as the heart turning a different color) to show that the movie has been unfavorited. +- [X] **Watched Checkbox** + - [X] For each movie displayed, users can mark the movie as watched. + - [X] There should be visual element (such as an eye icon) on each movie's tile to show whether or not the movie has been watched. + - [X] If the movie has not been watched: + - [X] Clicking on the visual element should mark the movie as watched + - [X] There should be visual feedback (such as the eye turning a different color) to show that the movie has been watched by the user. + - [X] If the movie is already watched: + - [X] Clicking on the visual element should mark the movie as *not* watched. + - [X] There should be visual feedback (such as the eye turning a different color) to show that the movie has not been watched. - [ ] **Sidebar** - - [ ] The website includes a side navigation bar. - - [ ] The sidebar has three pages: - - [ ] Home - - [ ] Favorites - - [ ] Watched - - [ ] The Home page displays all current movies in a grid view, the search bar, and the sort movies drop-down. - - [ ] The Favorites page displays all favorited movies in a grid view. - - [ ] The Watched page displays all watched movies in a grid view. + - [X] The website includes a side navigation bar. + - [X] The sidebar has three pages: + - [X] Home + - [X] Favorites + - [X] Watched + - [X] The Home page displays all current movies in a grid view, the search bar, and the sort movies drop-down. + - [X] The Favorites page displays all favorited movies in a grid view. + - [X] The Watched page displays all watched movies in a grid view. ### Walkthrough Video `TODO://` Add the embedded URL code to your animated app walkthrough below, `ADD_EMBEDDED_CODE_HERE`. Make sure the video or gif actually renders and animates when viewing this README. Ensure your walkthrough showcases the presence and/or functionality of all features you implemented above (check them off as you film!). Pay attention to any **VIDEO WALKTHROUGH SPECIAL INSTRUCTIONS** checkboxes listed above to ensure graders see the full functionality of your website! (🚫 Remove this paragraph after adding walkthrough video) -`ADD_EMBEDDED_CODE_HERE` +`https://www.loom.com/share/f0a95dc25e9a4c12b51135f2e0e097d4` ### Reflection From 354fc28c2c7641bb16bf045bc36bcd65c6536cce Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Fri, 13 Jun 2025 10:55:26 -0700 Subject: [PATCH 25/28] Completed Flixster_README for submission --- Flixster_README | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Flixster_README b/Flixster_README index 66a1cfac..f22fa1d4 100644 --- a/Flixster_README +++ b/Flixster_README @@ -90,7 +90,7 @@ Deployed Application (**required**): [Flixster Deployed Site](https://flixster-s - [X] If the movie is already watched: - [X] Clicking on the visual element should mark the movie as *not* watched. - [X] There should be visual feedback (such as the eye turning a different color) to show that the movie has not been watched. -- [ ] **Sidebar** +- [X] **Sidebar** - [X] The website includes a side navigation bar. - [X] The sidebar has three pages: - [X] Home @@ -110,15 +110,15 @@ Deployed Application (**required**): [Flixster Deployed Site](https://flixster-s * Did the topics discussed in your labs prepare you to complete the assignment? Be specific, which features in your weekly assignment did you feel unprepared to complete? -Add your response here +Yes, the useState and prop drilling labs definitely helped me feel prepared to complete the assignment, however, I would say that the useEffect lessons didn't prepare us enough to deal with race conditions in the project. I also felt unprepared to complete the API pulling and async functions because the labs didn't cover tricky situations with await/async and where we could use each kind of function. * If you had more time, what would you have done differently? Would you have added additional features? Changed the way your project responded to a particular event, etc. -Add your response here +If I had more time, I would've added a search bar the could search in the favorites and watched, but that would be more difficult because there isn't an API for it. I also would've used TMDB to store my watched/favorites list so it could be persistant even after page reloads. * Reflect on your project demo, what went well? Were there things that maybe didn't go as planned? Did you notice something that your peer did that you would like to try next time? -Add your response here +Didn't demo this week (I already demoed last week). Next week, I would like to try adding color gradients for my CSS styling and more reactive color changes. ### Open-source libraries used @@ -127,3 +127,4 @@ Add your response here ### Shout out Give a shout out to somebody from your cohort that especially helped you during your project. This can be a fellow peer, instructor, TA, mentor, etc. +Shoutout to Liam Bremm for providing helpful feedback on CSS styling. \ No newline at end of file From f6c0ed145536d5012e78923d6dd72ccfced2c7d3 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Fri, 13 Jun 2025 10:56:16 -0700 Subject: [PATCH 26/28] Completed Flixster_README for submission --- Flixster_README | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Flixster_README b/Flixster_README index f22fa1d4..3e4f3fe2 100644 --- a/Flixster_README +++ b/Flixster_README @@ -1,5 +1,3 @@ -📝 `NOTE` Use this template to initialize the contents of a README.md file for your application. As you work on your assignment over the course of the week, update the required or stretch features lists to indicate which features you have completed by changing `[ ]` to `[x]`. (🚫 Remove this paragraph before submitting your assignment.) - ## Unit Assignment: Flixster Submitted by: **Jack McClure** @@ -102,8 +100,6 @@ Deployed Application (**required**): [Flixster Deployed Site](https://flixster-s ### Walkthrough Video -`TODO://` Add the embedded URL code to your animated app walkthrough below, `ADD_EMBEDDED_CODE_HERE`. Make sure the video or gif actually renders and animates when viewing this README. Ensure your walkthrough showcases the presence and/or functionality of all features you implemented above (check them off as you film!). Pay attention to any **VIDEO WALKTHROUGH SPECIAL INSTRUCTIONS** checkboxes listed above to ensure graders see the full functionality of your website! (🚫 Remove this paragraph after adding walkthrough video) - `https://www.loom.com/share/f0a95dc25e9a4c12b51135f2e0e097d4` ### Reflection From b961f5f585a77a83261e43597ad8d8d4934f9f14 Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Fri, 13 Jun 2025 11:12:20 -0700 Subject: [PATCH 27/28] Reorganized components into individual folders --- src/App.jsx | 4 ++-- src/{ => components/LoadMoreBar}/LoadMoreBar.css | 0 src/{ => components/LoadMoreBar}/LoadMoreBar.jsx | 0 src/{ => components/MovieBox}/MovieBox.css | 0 src/{ => components/MovieBox}/MovieBox.jsx | 6 +++--- src/{ => components/MovieCard}/MovieCard.css | 0 src/{ => components/MovieCard}/MovieCard.jsx | 2 +- src/{ => components/MovieList}/MovieList.css | 0 src/{ => components/MovieList}/MovieList.jsx | 4 ++-- src/{ => components/MovieModal}/MovieModal.css | 0 src/{ => components/MovieModal}/MovieModal.jsx | 0 src/{ => components/SearchBar}/SearchBar.css | 0 src/{ => components/SearchBar}/SearchBar.jsx | 0 src/{ => components/SideBar}/SideBar.css | 0 src/{ => components/SideBar}/SideBar.jsx | 0 src/main.jsx | 2 +- src/{ => styles}/index.css | 0 src/utils/apiUtils.jsx | 0 src/utils/sortingUtils.js | 0 19 files changed, 9 insertions(+), 9 deletions(-) rename src/{ => components/LoadMoreBar}/LoadMoreBar.css (100%) rename src/{ => components/LoadMoreBar}/LoadMoreBar.jsx (100%) rename src/{ => components/MovieBox}/MovieBox.css (100%) rename src/{ => components/MovieBox}/MovieBox.jsx (98%) rename src/{ => components/MovieCard}/MovieCard.css (100%) rename src/{ => components/MovieCard}/MovieCard.jsx (95%) rename src/{ => components/MovieList}/MovieList.css (100%) rename src/{ => components/MovieList}/MovieList.jsx (91%) rename src/{ => components/MovieModal}/MovieModal.css (100%) rename src/{ => components/MovieModal}/MovieModal.jsx (100%) rename src/{ => components/SearchBar}/SearchBar.css (100%) rename src/{ => components/SearchBar}/SearchBar.jsx (100%) rename src/{ => components/SideBar}/SideBar.css (100%) rename src/{ => components/SideBar}/SideBar.jsx (100%) rename src/{ => styles}/index.css (100%) create mode 100644 src/utils/apiUtils.jsx create mode 100644 src/utils/sortingUtils.js diff --git a/src/App.jsx b/src/App.jsx index a34099f4..285ea787 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,7 +1,7 @@ import { useState } from 'react' import './App.css' -import SideBar from './SideBar' -import MovieBox from './MovieBox' +import SideBar from './components/SideBar/SideBar' +import MovieBox from './components/MovieBox/MovieBox' const App = () => { const [mode, setMode] = useState("now-playing") diff --git a/src/LoadMoreBar.css b/src/components/LoadMoreBar/LoadMoreBar.css similarity index 100% rename from src/LoadMoreBar.css rename to src/components/LoadMoreBar/LoadMoreBar.css diff --git a/src/LoadMoreBar.jsx b/src/components/LoadMoreBar/LoadMoreBar.jsx similarity index 100% rename from src/LoadMoreBar.jsx rename to src/components/LoadMoreBar/LoadMoreBar.jsx diff --git a/src/MovieBox.css b/src/components/MovieBox/MovieBox.css similarity index 100% rename from src/MovieBox.css rename to src/components/MovieBox/MovieBox.css diff --git a/src/MovieBox.jsx b/src/components/MovieBox/MovieBox.jsx similarity index 98% rename from src/MovieBox.jsx rename to src/components/MovieBox/MovieBox.jsx index ef62b5b3..3e05408c 100644 --- a/src/MovieBox.jsx +++ b/src/components/MovieBox/MovieBox.jsx @@ -1,6 +1,6 @@ -import SearchBar from './SearchBar' -import MovieList from './MovieList' -import LoadMoreBar from './LoadMoreBar' +import SearchBar from '../SearchBar/SearchBar' +import MovieList from '../MovieList/MovieList' +import LoadMoreBar from '../LoadMoreBar/LoadMoreBar' import './MovieBox.css' import { useEffect, useState } from 'react' diff --git a/src/MovieCard.css b/src/components/MovieCard/MovieCard.css similarity index 100% rename from src/MovieCard.css rename to src/components/MovieCard/MovieCard.css diff --git a/src/MovieCard.jsx b/src/components/MovieCard/MovieCard.jsx similarity index 95% rename from src/MovieCard.jsx rename to src/components/MovieCard/MovieCard.jsx index 84fbe73a..6895b31e 100644 --- a/src/MovieCard.jsx +++ b/src/components/MovieCard/MovieCard.jsx @@ -1,6 +1,6 @@ import './MovieCard.css' import { useState } from 'react' -import MovieModal from './MovieModal' +import MovieModal from '../MovieModal/MovieModal' import { BsEyeSlash, BsEyeFill } from "react-icons/bs"; import { FaStar, FaRegStar } from "react-icons/fa"; diff --git a/src/MovieList.css b/src/components/MovieList/MovieList.css similarity index 100% rename from src/MovieList.css rename to src/components/MovieList/MovieList.css diff --git a/src/MovieList.jsx b/src/components/MovieList/MovieList.jsx similarity index 91% rename from src/MovieList.jsx rename to src/components/MovieList/MovieList.jsx index f65135da..80e78ca3 100644 --- a/src/MovieList.jsx +++ b/src/components/MovieList/MovieList.jsx @@ -1,6 +1,6 @@ -import MovieCard from './MovieCard' +import MovieCard from '../MovieCard/MovieCard' import './MovieList.css' -import MovieModal from './MovieModal' +import MovieModal from '../MovieModal/MovieModal' import { useState } from 'react' const MovieList = ( {movies, order, favorites, watched, toggleFavorite, toggleWatched} ) => { diff --git a/src/MovieModal.css b/src/components/MovieModal/MovieModal.css similarity index 100% rename from src/MovieModal.css rename to src/components/MovieModal/MovieModal.css diff --git a/src/MovieModal.jsx b/src/components/MovieModal/MovieModal.jsx similarity index 100% rename from src/MovieModal.jsx rename to src/components/MovieModal/MovieModal.jsx diff --git a/src/SearchBar.css b/src/components/SearchBar/SearchBar.css similarity index 100% rename from src/SearchBar.css rename to src/components/SearchBar/SearchBar.css diff --git a/src/SearchBar.jsx b/src/components/SearchBar/SearchBar.jsx similarity index 100% rename from src/SearchBar.jsx rename to src/components/SearchBar/SearchBar.jsx diff --git a/src/SideBar.css b/src/components/SideBar/SideBar.css similarity index 100% rename from src/SideBar.css rename to src/components/SideBar/SideBar.css diff --git a/src/SideBar.jsx b/src/components/SideBar/SideBar.jsx similarity index 100% rename from src/SideBar.jsx rename to src/components/SideBar/SideBar.jsx diff --git a/src/main.jsx b/src/main.jsx index 54b39dd1..fce89a24 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,7 +1,7 @@ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.jsx' -import './index.css' +import './styles/index.css' ReactDOM.createRoot(document.getElementById('root')).render( diff --git a/src/index.css b/src/styles/index.css similarity index 100% rename from src/index.css rename to src/styles/index.css diff --git a/src/utils/apiUtils.jsx b/src/utils/apiUtils.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/utils/sortingUtils.js b/src/utils/sortingUtils.js new file mode 100644 index 00000000..e69de29b From 2cb3a84897affe73de549dc58e79be6bba4fc44f Mon Sep 17 00:00:00 2001 From: Jack McClure Date: Fri, 13 Jun 2025 11:44:18 -0700 Subject: [PATCH 28/28] Reorganized file structure - each component now has it's own folder for jsx and css Fix - sorting mode now resets when swapping to favorites or watched --- src/components/MovieBox/MovieBox.jsx | 66 +++--------------------- src/components/MovieModal/MovieModal.jsx | 55 ++++---------------- src/utils/apiUtils.jsx | 66 ++++++++++++++++++++++++ src/utils/sortingUtils.js | 29 +++++++++++ 4 files changed, 112 insertions(+), 104 deletions(-) diff --git a/src/components/MovieBox/MovieBox.jsx b/src/components/MovieBox/MovieBox.jsx index 3e05408c..a59101aa 100644 --- a/src/components/MovieBox/MovieBox.jsx +++ b/src/components/MovieBox/MovieBox.jsx @@ -3,6 +3,8 @@ import MovieList from '../MovieList/MovieList' import LoadMoreBar from '../LoadMoreBar/LoadMoreBar' import './MovieBox.css' import { useEffect, useState } from 'react' +import { movieEntriesTitleSort, movieEntriesReleaseDateSort, movieEntriesVoteAverageSort } from '../../utils/sortingUtils' +import { fetchMovieByID, fetchMoviesBySearch } from '../../utils/apiUtils' //review1-branch @@ -25,25 +27,6 @@ const MovieBox = (mode) => { // Takes in a list of movieIDs, fetches corresponding movies from TMDB // Replaces the movies, order, and originalOrder states const fetchAndProcessMoviesByIDList = async (movieIDList) => { - const fetchMovieByID = async (movieID) => { - try { - const apiKey = import.meta.env.VITE_APP_API_KEY - const options = { - method: 'GET', - headers: { - accept: 'application/json', - Authorization: `Bearer ${apiKey}` - } - }; - let response = null - response = await fetch(`https://api.themoviedb.org/3/movie/${movieID}?language=en-US`, options) - if (!response.ok) {throw new Error('Failed to fetch movie by ID')} - const data = await response.json() - return data - } catch (error) { - console.error(error) - } - } const processMoviesByID = (movieData) => { let movieDict = {} movieData.map((movie) => movieDict[movie.id] = movie) @@ -68,30 +51,6 @@ const MovieBox = (mode) => { // Takes in the page (and searchQuery) states to fetch relevant movies from TMDB // Replaces the movies, order, and originalOrder states const fetchAndProcessMoviesBySearch = async () => { - const buildMovieSearchURL = () => { - return searchQuery === "" - ? `https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${page}&include_adult=false` - :`https://api.themoviedb.org/3/search/movie?query=${searchQuery}&include_adult=false&language=en-US&page=${page}` - } - const fetchMoviesBySearch = async () => { - try { - const apiKey = import.meta.env.VITE_APP_API_KEY - const options = { - method: 'GET', - headers: { - accept: 'application/json', - Authorization: `Bearer ${apiKey}` - } - }; - const searchURL = buildMovieSearchURL() - let response = await fetch(searchURL, options) - if (!response.ok) {throw new Error('Failed to fetch movies by search')} - const data = await response.json() - return data - } catch (error) { - console.error(error) - } - } const processMoviesBySearch = (moviesData) => { if (moviesData.total_pages === page) {setMorePages(false)} let newMovies = {} @@ -110,7 +69,7 @@ const MovieBox = (mode) => { setMovies(newMovies) } - let moviesData = await fetchMoviesBySearch() + let moviesData = await fetchMoviesBySearch(page, searchQuery) processMoviesBySearch(moviesData) } @@ -125,25 +84,13 @@ const MovieBox = (mode) => { // Listened to feedback about using switch/case for repeated if/else if or where it's easier to read switch (sortMode) { case "title": - movieEntries.sort((left_entry, right_entry) => { - if (left_entry[1].title < right_entry[1].title) {return -1;} - if (left_entry[1].title > right_entry[1].title) {return 1;} - return 0; - }) + movieEntriesTitleSort(movieEntries) break; case "release": - // Listened to feedback about shortening sorting comparator - // Note: can't subtract strings in JS - movieEntries.sort((left_entry, right_entry) => { - const left_date = new Date(left_entry[1].release_date) - const right_date = new Date(right_entry[1].release_date) - return right_date - left_date - }) + movieEntriesReleaseDateSort(movieEntries) break; case "vote": - movieEntries.sort((left_entry, right_entry) => { - return right_entry[1].vote_average-left_entry[1].vote_average - }) + movieEntriesVoteAverageSort(movieEntries) break; case "none": setOrder(originalOrder) @@ -168,6 +115,7 @@ const MovieBox = (mode) => { updateSearchText("") setPageCleared(true) } + setSortMode("none") }, [mode]) // Uses the mode change as a trigger to grab the relevant movies for new mode diff --git a/src/components/MovieModal/MovieModal.jsx b/src/components/MovieModal/MovieModal.jsx index 9fff6159..4c1b3157 100644 --- a/src/components/MovieModal/MovieModal.jsx +++ b/src/components/MovieModal/MovieModal.jsx @@ -1,61 +1,26 @@ import './MovieModal.css' import { useState, useEffect } from 'react' +import { fetchMovieByID, fetchMovieVideosByID } from '../../utils/apiUtils' const MovieModal = ({movieID, closeModal}) => { const [movie, setMovie] = useState({}) const [trailers, setTrailers] = useState([]) const [currentTrailer, setCurrentTrailer] = useState(0) - // Fetches movie from movieID - const fetchMovie = async (movieID) => { - try { - const apiKey = import.meta.env.VITE_APP_API_KEY - const options = { - method: 'GET', - headers: { - accept: 'application/json', - Authorization: `Bearer ${apiKey}` - } - }; - let response = null - response = await fetch(`https://api.themoviedb.org/3/movie/${movieID}`, options) - if (!response.ok) { - throw new Error('Failed to fetch movies') - } - const data = await response.json() - setMovie(data) - } catch (error) { - console.error(error) - } + const fetchAndProcessMovieDetails = async (movieID) => { + const movieDetails = await fetchMovieByID(movieID) + setMovie(movieDetails) } - // Fetches trailers from movieID - const fetchTrailers = async (movieID) => { - try { - const apiKey = import.meta.env.VITE_APP_API_KEY - const options = { - method: 'GET', - headers: { - accept: 'application/json', - Authorization: `Bearer ${apiKey}` - } - }; - let response = null - response = await fetch(`https://api.themoviedb.org/3/movie/${movieID}/videos?language=en-US`, options) - if (!response.ok) { - throw new Error('Failed to fetch videos') - } - const data = await response.json() - const trailers = data.results.filter( (video) => {return video.type === "Trailer"}) - setTrailers(trailers) - } catch (error) { - console.error(error) - } + const fetchAndProcessMovieTrailers = async (movieID) => { + const movieVideos = await fetchMovieVideosByID(movieID) + const trailers = movieVideos.results.filter( (video) => {return video.type === "Trailer"}) + setTrailers(trailers) } useEffect( () => { - fetchMovie(movieID) - fetchTrailers(movieID) + fetchAndProcessMovieDetails(movieID) + fetchAndProcessMovieTrailers(movieID) }, []) return ( diff --git a/src/utils/apiUtils.jsx b/src/utils/apiUtils.jsx index e69de29b..4b97c2dd 100644 --- a/src/utils/apiUtils.jsx +++ b/src/utils/apiUtils.jsx @@ -0,0 +1,66 @@ +export const fetchMovieByID = async (movieID) => { + try { + const apiKey = import.meta.env.VITE_APP_API_KEY + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: `Bearer ${apiKey}` + } + }; + let response = null + response = await fetch(`https://api.themoviedb.org/3/movie/${movieID}?language=en-US`, options) + if (!response.ok) {throw new Error('Failed to fetch movie by ID')} + const data = await response.json() + return data + } catch (error) { + console.error(error) + } +} + +export const fetchMoviesBySearch = async (page, searchQuery) => { + const buildMovieSearchURL = (page, searchQuery) => { + return searchQuery === "" + ? `https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${page}&include_adult=false` + :`https://api.themoviedb.org/3/search/movie?query=${searchQuery}&include_adult=false&language=en-US&page=${page}` + } + try { + const apiKey = import.meta.env.VITE_APP_API_KEY + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: `Bearer ${apiKey}` + } + }; + const searchURL = buildMovieSearchURL(page, searchQuery) + let response = await fetch(searchURL, options) + if (!response.ok) {throw new Error('Failed to fetch movies by search')} + const data = await response.json() + return data + } catch (error) { + console.error(error) + } +} + +export const fetchMovieVideosByID = async (movieID) => { + try { + const apiKey = import.meta.env.VITE_APP_API_KEY + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: `Bearer ${apiKey}` + } + }; + let response = null + response = await fetch(`https://api.themoviedb.org/3/movie/${movieID}/videos?language=en-US`, options) + if (!response.ok) { + throw new Error('Failed to fetch videos') + } + const data = await response.json() + return data + } catch (error) { + console.error(error) + } +} \ No newline at end of file diff --git a/src/utils/sortingUtils.js b/src/utils/sortingUtils.js index e69de29b..9072eaa6 100644 --- a/src/utils/sortingUtils.js +++ b/src/utils/sortingUtils.js @@ -0,0 +1,29 @@ +// Takes a list of movie entries (a list of {id: int, movie: {}}) and sorts alphabetically by title property +export const movieEntriesTitleSort = (movieEntries) => { + movieEntries.sort((left_entry, right_entry) => { + if (left_entry[1].title < right_entry[1].title) {return -1;} + if (left_entry[1].title > right_entry[1].title) {return 1;} + return 0; + }) + return movieEntries +} + +// Takes a list of movie entries (a list of {id: int, movie: {}}) and sorts by release date property (newest to oldest) +export const movieEntriesReleaseDateSort = (movieEntries) => { + // Listened to feedback about shortening sorting comparator + // Note: can't subtract strings in JS + movieEntries.sort((left_entry, right_entry) => { + const left_date = new Date(left_entry[1].release_date) + const right_date = new Date(right_entry[1].release_date) + return right_date - left_date + }) + return movieEntries +} + +// Takes a list of movie entries (a list of {id: int, movie: {}}) and sorts by vote average property (highest to lowest) +export const movieEntriesVoteAverageSort = (movieEntries) => { + movieEntries.sort((left_entry, right_entry) => { + return right_entry[1].vote_average-left_entry[1].vote_average + }) + return movieEntries +} \ No newline at end of file