From a212b3bec92e72a9f82a21521a10cdafe332d368 Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Tue, 10 Jun 2025 11:17:12 -0700 Subject: [PATCH 01/19] feat: created MovieCard component that displays data from API --- .env | 1 + package-lock.json | 671 ++++++++++++++++++++++++---------------------- src/App.jsx | 19 +- src/MovieCard.css | 0 src/MovieCard.jsx | 25 ++ 5 files changed, 400 insertions(+), 316 deletions(-) create mode 100644 .env create mode 100644 src/MovieCard.css create mode 100644 src/MovieCard.jsx diff --git a/.env b/.env new file mode 100644 index 00000000..3f2e75c2 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +VITE_API_KEY = 464a3d2aadb72eb600aea451534369b7 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.jsx b/src/App.jsx index dfa91584..bb692fda 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,10 +1,27 @@ import { useState } from 'react' import './App.css' +import MovieCard from './MovieCard' +import data from './data/data' const App = () => { + // Get the movies from the data + const movies = data.results; + return (
- +
+

Flixster

+
+
+ {movies.map(movie => ( + + ))} +
) } diff --git a/src/MovieCard.css b/src/MovieCard.css new file mode 100644 index 00000000..e69de29b diff --git a/src/MovieCard.jsx b/src/MovieCard.jsx new file mode 100644 index 00000000..c1881558 --- /dev/null +++ b/src/MovieCard.jsx @@ -0,0 +1,25 @@ +// import React from 'react'; +import './MovieCard.css'; + +const MovieCard = ({ movie }) => { + + const posterBaseUrl = "https://image.tmdb.org/t/p/w500"; + + return ( +
+ {`${movie.title} +
+

{movie.title}

+
+ {movie.vote_average.toFixed(1)} +
+
+
+ ); +}; + +export default MovieCard; From d5fe3a0f38c1ee2d76809484ebf80adf61cd52ca Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Tue, 10 Jun 2025 11:30:37 -0700 Subject: [PATCH 02/19] feat: Added movielist component feat: styled movieList & MovieCard --- .gitignore | 2 ++ src/App.jsx | 19 +++------------- src/MovieCard.css | 46 +++++++++++++++++++++++++++++++++++++++ src/MovieList.css | 39 +++++++++++++++++++++++++++++++++ src/MovieList.jsx | 55 +++++++++++++++++++++++++++++++++++++++++++++++ src/index.css | 2 +- 6 files changed, 146 insertions(+), 17 deletions(-) create mode 100644 src/MovieList.css create mode 100644 src/MovieList.jsx diff --git a/.gitignore b/.gitignore index a547bf36..e4d85bc6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ dist dist-ssr *.local + # Editor directories and files .vscode/* !.vscode/extensions.json @@ -22,3 +23,4 @@ dist-ssr *.njsproj *.sln *.sw? +.env diff --git a/src/App.jsx b/src/App.jsx index bb692fda..344337bd 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,27 +1,14 @@ -import { useState } from 'react' import './App.css' -import MovieCard from './MovieCard' -import data from './data/data' +import MovieList from './MovieList' const App = () => { - // Get the movies from the data - const movies = data.results; - return (

Flixster

+ inp
-
- {movies.map(movie => ( - - ))} -
+
) } diff --git a/src/MovieCard.css b/src/MovieCard.css index e69de29b..70472a7b 100644 --- a/src/MovieCard.css +++ b/src/MovieCard.css @@ -0,0 +1,46 @@ +.movie-card { + width: 200px; + margin: 1rem; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + transition: transform 0.3s ease; + background-color: #1a1a1a; +} + +.movie-card:hover { + transform: scale(1.03); +} + +.movie-poster { + width: 100%; + height: 300px; + object-fit: cover; +} + +.movie-info { + padding: 1rem; +} + +.movie-title { + margin: 0 0 0.5rem 0; + font-size: 1rem; + color: white; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.movie-vote { + display: flex; + align-items: center; +} + +.vote-average { + background-color: #ffd700; + color: black; + padding: 0.2rem 0.5rem; + border-radius: 4px; + font-weight: bold; + font-size: 0.9rem; +} diff --git a/src/MovieList.css b/src/MovieList.css new file mode 100644 index 00000000..a3a4c73c --- /dev/null +++ b/src/MovieList.css @@ -0,0 +1,39 @@ +.movie-list-container { + padding: 20px; +} + +.section-title { + font-size: 1.8rem; + margin-bottom: 20px; + color: #fff; + text-align: center; +} + +.movie-list { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 20px; +} + +.loading, .error { + text-align: center; + padding: 40px; + font-size: 1.2rem; + color: #fff; +} + +.error { + color: #ff6b6b; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .movie-list { + gap: 15px; + } + + .section-title { + font-size: 1.5rem; + } +} diff --git a/src/MovieList.jsx b/src/MovieList.jsx new file mode 100644 index 00000000..84520896 --- /dev/null +++ b/src/MovieList.jsx @@ -0,0 +1,55 @@ +import { useState, useEffect } from 'react'; +import MovieCard from './MovieCard'; +import './MovieList.css'; + +const MovieList = () => { + const [movies, setMovies] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchMovies = async () => { + const apiKey = import.meta.env.VITE_API_KEY; + const url = `https://api.themoviedb.org/3/movie/now_playing?api_key=${apiKey}&language=en-US&page=1`; + + try { + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const data = await response.json(); + setMovies(data.results); + setLoading(false); + } catch (error) { + console.error("Error fetching movies:", error); + setError("Failed to fetch movies. Please try again later."); + setLoading(false); + } + }; + + fetchMovies(); + }, []); + + if (loading) { + return
Loading movies...
; + } + + if (error) { + return
{error}
; + } + + return ( +
+

Now Playing

+
+ {movies.map(movie => ( + + ))} +
+
+ ); +}; + +export default MovieList; diff --git a/src/index.css b/src/index.css index e1faed1a..81dfc73b 100644 --- a/src/index.css +++ b/src/index.css @@ -1,7 +1,7 @@ body { margin: 0; font-family: Arial, sans-serif; - background-color: #f4f4f4; + background-color: #b4b1b1; } button { From 7c520e9cfa04c69392ae771bc61322e21cc46dcd Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Tue, 10 Jun 2025 11:35:31 -0700 Subject: [PATCH 03/19] Created search-form --- src/SearchForm.jsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/SearchForm.jsx diff --git a/src/SearchForm.jsx b/src/SearchForm.jsx new file mode 100644 index 00000000..e69de29b From 1cfb8fbe21bf0b160ec89e3fcda193827fa179f1 Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Tue, 10 Jun 2025 13:47:49 -0700 Subject: [PATCH 04/19] feat: IMPLEMENTED WEBSITE DESIGN, chose color scheme and website theme. Basic formatting --- index.html | 3 ++ src/App.css | 8 +++-- src/App.jsx | 5 +++- src/MovieCard.css | 14 ++++++--- src/MovieList.css | 7 ++++- src/SearchForm.css | 74 ++++++++++++++++++++++++++++++++++++++++++++++ src/SearchForm.jsx | 16 ++++++++++ src/SortForm.css | 16 ++++++++++ src/SortForm.jsx | 14 +++++++++ src/index.css | 26 +++++++++++++--- 10 files changed, 171 insertions(+), 12 deletions(-) create mode 100644 src/SearchForm.css create mode 100644 src/SortForm.css create mode 100644 src/SortForm.jsx diff --git a/index.html b/index.html index 0abcfe5f..f066eca2 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,9 @@ + + + Flixster diff --git a/src/App.css b/src/App.css index 0bf65669..92fe6961 100644 --- a/src/App.css +++ b/src/App.css @@ -9,7 +9,11 @@ align-items: center; justify-content: space-evenly; color: white; - padding: 20px; + padding: 1.5rem; + + position: fixed; + width: 100%; + z-index: 1000; } @media (max-width: 600px) { @@ -19,7 +23,7 @@ .search-bar { flex-direction: column; - gap: 10px; + gap: 1rem; } .search-bar form { diff --git a/src/App.jsx b/src/App.jsx index 344337bd..37327bb8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,12 +1,15 @@ import './App.css' import MovieList from './MovieList' +import SearchForm from './SearchForm' +import SortForm from './SortForm' const App = () => { return (

Flixster

- inp + +
diff --git a/src/MovieCard.css b/src/MovieCard.css index 70472a7b..0a0b7563 100644 --- a/src/MovieCard.css +++ b/src/MovieCard.css @@ -1,15 +1,19 @@ .movie-card { width: 200px; margin: 1rem; - border-radius: 8px; + border-radius: 0.8rem; overflow: hidden; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); - transition: transform 0.3s ease; + transition: transform 0.3s ease, + box-shadow 0.3s ease; background-color: #1a1a1a; + cursor: pointer; + /* z-index: */ } .movie-card:hover { transform: scale(1.03); + box-shadow: 0rem 0.15rem 0.9rem rgba(229, 9, 20); + } .movie-poster { @@ -34,12 +38,14 @@ .movie-vote { display: flex; align-items: center; + /* text-align: center; */ } .vote-average { - background-color: #ffd700; + background-color: #fa5252; color: black; padding: 0.2rem 0.5rem; + margin: 0.05rem auto; border-radius: 4px; font-weight: bold; font-size: 0.9rem; diff --git a/src/MovieList.css b/src/MovieList.css index a3a4c73c..9eb7784b 100644 --- a/src/MovieList.css +++ b/src/MovieList.css @@ -1,5 +1,5 @@ .movie-list-container { - padding: 20px; + padding: 11rem 3.5rem; } .section-title { @@ -7,6 +7,11 @@ margin-bottom: 20px; color: #fff; text-align: center; + text-transform: uppercase; + letter-spacing: 0.15rem; + word-spacing: 0.5rem; + /* color: rgb(229, 9, 20) */ + color: #fa5252; } .movie-list { diff --git a/src/SearchForm.css b/src/SearchForm.css new file mode 100644 index 00000000..721ea7c4 --- /dev/null +++ b/src/SearchForm.css @@ -0,0 +1,74 @@ +.search-container +{ + width: 100%; + + + display: flex; + align-items: center; + justify-content: center; + height: 7rem; + /* gap: 1rem; */ +} + +.search-bar[type="text"] +{ + + border: 0.1rem solid rgba(250, 82, 82, 0.6); + border-right: none; + width: 85rem; + + padding: 1rem 1rem; + /* margin-right: 2rem; */ + border-radius: 8rem 0rem 0rem 8rem; + background-color: #212529; +} + +.search-bar::placeholder +{ + padding-left: 2rem; + color: rgb(250, 82, 82, 0.6); + /* background-color: #868e96; */ +} + +.search-bar::after +{ + +} + + +.search-button +{ + height: 4.05rem; + border: 0.1rem solid rgba(250, 82, 82, 0.6); + border-radius: 0rem 8rem 8rem 0rem; + + border-left: none; + background-color: #1a1e21; + color: rgb(250, 82, 82, 0.8); + /* padding: 1.2rem 2.4rem; */ + padding: 0.5rem 1rem; + cursor: pointer; + place-content: center; + + margin-right: 2.5rem; +} + +/* .search-button:hover +{ + background-color: 373b3e; +} */ + +.clear-button +{ + height: 4.05rem; + border: 0.1rem solid rgba(250, 82, 82, 0.6); + border-radius: 8rem; + + + background-color: #1a1e21; + color: rgb(250, 82, 82, 0.8); + padding: 0 1.5rem; + + cursor: pointer; + place-content: center; +} diff --git a/src/SearchForm.jsx b/src/SearchForm.jsx index e69de29b..98a60464 100644 --- a/src/SearchForm.jsx +++ b/src/SearchForm.jsx @@ -0,0 +1,16 @@ +import './SearchForm.css'; + +const SearchForm = () => + { + return( +
+
+ + + +
+
+ ) + } + +export default SearchForm; diff --git a/src/SortForm.css b/src/SortForm.css new file mode 100644 index 00000000..dd8e92ad --- /dev/null +++ b/src/SortForm.css @@ -0,0 +1,16 @@ +.sort-button + +{ + height: 4.05rem; + + border: 0.1rem solid rgba(250, 82, 82, 0.6); + border-radius: 1rem; + + + background-color: #1a1e21; + color: rgb(250, 82, 82, 0.8); + padding: 0 3.5rem; + + cursor: pointer; + place-content: center; +} diff --git a/src/SortForm.jsx b/src/SortForm.jsx new file mode 100644 index 00000000..60d6e725 --- /dev/null +++ b/src/SortForm.jsx @@ -0,0 +1,14 @@ +import './SortForm.css' + +const SortForm = () => + { + return ( +
+ +
+ ) + } + +export default SortForm diff --git a/src/index.css b/src/index.css index 81dfc73b..1580fedf 100644 --- a/src/index.css +++ b/src/index.css @@ -1,14 +1,25 @@ body { margin: 0; - font-family: Arial, sans-serif; - background-color: #b4b1b1; + font-family: 'Rubik', sans-serif; + background-color: #212529; + } + +* +{ + /* color: rgb(229, 9, 20); */ + color: #fa5252; + font-size: 1.6rem; + box-sizing: border-box; +} + + button { background-color: #282c34; - color: white; + color: #fa5252; cursor: pointer; - font-size: 16px; + font-size: 1.6rem; font-weight: bold; transition: background-color 0.3s ease; } @@ -17,3 +28,10 @@ button:hover { background-color: #777; color: white; } + + + +html +{ + font-size: 62.5%; +} From 0e35622eec17ff8fa08d92b4487b727ffcb871a3 Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Tue, 10 Jun 2025 15:55:48 -0700 Subject: [PATCH 05/19] feat: added load more button added search functionality to filter movies based on search and clear added switch function to switch between now playing and search results --- src/App.css | 7 ++- src/App.jsx | 23 +++++++-- src/MovieList.css | 60 ++++++++++++++++++++++ src/MovieList.jsx | 123 +++++++++++++++++++++++++++++++++++++++------ src/SearchForm.css | 50 ++++++++++++++---- src/SearchForm.jsx | 44 +++++++++++----- 6 files changed, 267 insertions(+), 40 deletions(-) diff --git a/src/App.css b/src/App.css index 92fe6961..170195cf 100644 --- a/src/App.css +++ b/src/App.css @@ -1,5 +1,6 @@ .App { text-align: center; + /* width: 70%; */ } .App-header { @@ -22,11 +23,15 @@ } .search-bar { - flex-direction: column; + /* flex-direction: column; */ gap: 1rem; } .search-bar form { flex-direction: column; } + + .App-header { + flex-direction: column; + } } diff --git a/src/App.jsx b/src/App.jsx index 37327bb8..19690817 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,17 +1,34 @@ +import { useState } from 'react' import './App.css' import MovieList from './MovieList' import SearchForm from './SearchForm' import SortForm from './SortForm' const App = () => { + const [searchQuery, setSearchQuery] = useState(''); + const [view, setView] = useState('nowPlaying'); // 'nowPlaying' or 'search' + + const handleSearch = (query) => { + setSearchQuery(query); + setView('search'); + }; + + const handleViewToggle = (selectedView) => { + setView(selectedView); + }; + return (

Flixster

- - + +
- +
) } diff --git a/src/MovieList.css b/src/MovieList.css index 9eb7784b..ebbb09fe 100644 --- a/src/MovieList.css +++ b/src/MovieList.css @@ -2,6 +2,38 @@ padding: 11rem 3.5rem; } +.view-toggle { + display: flex; + justify-content: center; + margin-bottom: 20px; +} + +.toggle-button { + background-color: #333; + color: #fff; + border: 1px solid #555; + padding: 8px 16px; + margin: 0 5px; + cursor: pointer; + border-radius: 4px; + font-size: 1rem; + transition: all 0.3s ease; +} + +.toggle-button:hover { + background-color: #444; +} + +.toggle-button.active { + background-color: #fa5252; + border-color: #fa5252; +} + +.toggle-button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + .section-title { font-size: 1.8rem; margin-bottom: 20px; @@ -32,6 +64,34 @@ color: #ff6b6b; } +.no-results { + text-align: center; + padding: 40px; + font-size: 1.2rem; + color: #fff; +} + +.load-more-container { + display: flex; + justify-content: center; + margin-top: 30px; +} + +.load-more-button { + background-color: #fa5252; + color: white; + border: none; + padding: 10px 20px; + border-radius: 4px; + font-size: 1rem; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.load-more-button:hover { + background-color: #e03131; +} + /* Responsive adjustments */ @media (max-width: 768px) { .movie-list { diff --git a/src/MovieList.jsx b/src/MovieList.jsx index 84520896..a1de87d0 100644 --- a/src/MovieList.jsx +++ b/src/MovieList.jsx @@ -2,15 +2,60 @@ import { useState, useEffect } from 'react'; import MovieCard from './MovieCard'; import './MovieList.css'; -const MovieList = () => { +const MovieList = ({ searchQuery, view, onViewToggle }) => { const [movies, setMovies] = useState([]); const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [page, setPage] = useState(1); + const [hasMore, setHasMore] = useState(true); + + const [searchResults, setSearchResults] = useState([]); + + const fetchMovies = async (pageNum = 1) => { + const apiKey = import.meta.env.VITE_API_KEY; + const url = `https://api.themoviedb.org/3/movie/now_playing?api_key=${apiKey}&language=en-US&page=${pageNum}`; + + try { + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const data = await response.json(); + + if (pageNum === 1) { + setMovies(data.results); + } else { + setMovies(prevMovies => [...prevMovies, ...data.results]); + } + + // this is for checking if there are more pages to load + setHasMore(data.page < data.total_pages); + setLoading(false); + } catch (error) { + console.error("Error fetching movies:", error); + setError("Failed to fetch movies. Please try again later."); + setLoading(false); + } + }; + const handleLoadMore = () => { + const nextPage = page + 1; + setPage(nextPage); + fetchMovies(nextPage); + }; + + // search for more movies when the search query changes useEffect(() => { - const fetchMovies = async () => { + const searchMovies = async () => { + if (!searchQuery.trim()) return; + + setLoading(true); + const apiKey = import.meta.env.VITE_API_KEY; - const url = `https://api.themoviedb.org/3/movie/now_playing?api_key=${apiKey}&language=en-US&page=1`; + const url = `https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&language=en-US&query=${encodeURIComponent(searchQuery)}&page=1`; try { const response = await fetch(url); @@ -20,19 +65,35 @@ const MovieList = () => { } const data = await response.json(); - setMovies(data.results); + setSearchResults(data.results); setLoading(false); } catch (error) { - console.error("Error fetching movies:", error); - setError("Failed to fetch movies. Please try again later."); + console.error("Error searching movies:", error); + setError("Failed to search movies. Please try again later."); setLoading(false); } }; - fetchMovies(); - }, []); + if (view === 'search') { + searchMovies(); + } + }, [searchQuery, view]); + + const handleViewToggle = (selectedView) => { + onViewToggle(selectedView); + if (selectedView === 'nowPlaying' && movies.length === 0) { + fetchMovies(1); + } + }; - if (loading) { + // initial fetch of now playing movies + useEffect(() => { + if (view === 'nowPlaying') { + fetchMovies(1); + } + }, [view]); + + if (loading && page === 1) { return
Loading movies...
; } @@ -40,14 +101,46 @@ const MovieList = () => { return
{error}
; } + const displayedMovies = view === 'nowPlaying' ? movies : searchResults; + const title = view === 'nowPlaying' ? 'Now Playing' : `Search Results for "${searchQuery}"`; + return (
-

Now Playing

-
- {movies.map(movie => ( - - ))} -
+
+ + +
+ +

{title}

+ + {displayedMovies.length === 0 && !loading ? ( +
No movies found
+ ) : ( +
+ {displayedMovies.map(movie => ( + + ))} +
+ )} + + {view === 'nowPlaying' && hasMore && !loading && ( +
+ +
+ )}
); }; diff --git a/src/SearchForm.css b/src/SearchForm.css index 721ea7c4..e7e4678a 100644 --- a/src/SearchForm.css +++ b/src/SearchForm.css @@ -3,7 +3,7 @@ width: 100%; - display: flex; + /* display: flex; */ align-items: center; justify-content: center; height: 7rem; @@ -30,11 +30,6 @@ /* background-color: #868e96; */ } -.search-bar::after -{ - -} - .search-button { @@ -51,12 +46,14 @@ place-content: center; margin-right: 2.5rem; + transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out; } -/* .search-button:hover +.search-button:hover { - background-color: 373b3e; -} */ + background-color: #333; + color: #ffffffe4; +} .clear-button { @@ -72,3 +69,38 @@ cursor: pointer; place-content: center; } + +@media (max-width: 1200px) { /* web */ + .search-container { + width: 60%; + } + .search-bar { + width: 20rem; + } + .searh-form{ + width: 40%; + } +} +@media (max-width: 600px) { /* tablet or ipad*/ + .search-container { + + } + .search-bar { + + } + .searh-form{ + width: 50%; + } +} +@media (max-width: 300px) { /* phone */ + .search-container { + + } + .search-bar { + + } + .searh-form{ + width: 30%; + } + +} */ diff --git a/src/SearchForm.jsx b/src/SearchForm.jsx index 98a60464..d6cb4aa8 100644 --- a/src/SearchForm.jsx +++ b/src/SearchForm.jsx @@ -1,16 +1,36 @@ +import { useState } from 'react'; import './SearchForm.css'; -const SearchForm = () => - { - return( -
-
- - - -
-
- ) - } +const SearchForm = ({ onSearch }) => { + const [query, setQuery] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + if (query.trim()) { + onSearch(query); + } + }; + + const handleClear = (e) => { + e.preventDefault(); + setQuery(''); + }; + + return ( +
+
+ setQuery(e.target.value)} + /> + + +
+
+ ); +}; export default SearchForm; From d07ab116addbea466d1968661bddfbefa8bd9679 Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Tue, 10 Jun 2025 16:58:29 -0700 Subject: [PATCH 06/19] Added responsive design for Add.header --- src/App.css | 9 ++++++--- src/SearchForm.css | 33 ++++++++------------------------- 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/src/App.css b/src/App.css index 170195cf..4b8b7db2 100644 --- a/src/App.css +++ b/src/App.css @@ -8,7 +8,7 @@ display: flex; flex-direction: row; align-items: center; - justify-content: space-evenly; + justify-content: space-between; color: white; padding: 1.5rem; @@ -17,13 +17,14 @@ z-index: 1000; } -@media (max-width: 600px) { +@media (max-width: 900px) { .movie-card { width: 100%; } + .search-bar { - /* flex-direction: column; */ + flex-direction: column; gap: 1rem; } @@ -33,5 +34,7 @@ .App-header { flex-direction: column; + align-items: center; + justify-content: center; } } diff --git a/src/SearchForm.css b/src/SearchForm.css index e7e4678a..080439d0 100644 --- a/src/SearchForm.css +++ b/src/SearchForm.css @@ -1,9 +1,15 @@ +.search-form +{ + display:flex; + align-items: center; + justify-content: center; +} .search-container { width: 100%; - /* display: flex; */ + display: flex; align-items: center; justify-content: center; height: 7rem; @@ -15,7 +21,7 @@ border: 0.1rem solid rgba(250, 82, 82, 0.6); border-right: none; - width: 85rem; + width: 50vw; padding: 1rem 1rem; /* margin-right: 2rem; */ @@ -81,26 +87,3 @@ width: 40%; } } -@media (max-width: 600px) { /* tablet or ipad*/ - .search-container { - - } - .search-bar { - - } - .searh-form{ - width: 50%; - } -} -@media (max-width: 300px) { /* phone */ - .search-container { - - } - .search-bar { - - } - .searh-form{ - width: 30%; - } - -} */ From 915b10f11e8f7e6b5e873e0299f6e04240f347b7 Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Tue, 10 Jun 2025 19:06:53 -0700 Subject: [PATCH 07/19] feat: add MovieModal and CSS for dynamic movie detail popup, updated clear button - Implemented MovieModal component with escape key and overlay click-to-close behavior - Added responsive modal styling with Netflix-style animation - Integrated backdrop, metadata (genres, rating, runtime), and overview sections -Updated clear button to also clear the movies being displayed when clicked --- .gitignore | 1 - public/placeholder-poster.jpg | 1 + public/placeholder-poster.svg | 7 ++ src/App.jsx | 7 +- src/MovieCard.jsx | 12 ++- src/MovieList.jsx | 27 +++++- src/MovieModal.css | 173 ++++++++++++++++++++++++++++++++++ src/MovieModal.jsx | 112 ++++++++++++++++++++++ src/SearchForm.jsx | 3 +- 9 files changed, 331 insertions(+), 12 deletions(-) create mode 100644 public/placeholder-poster.jpg create mode 100644 public/placeholder-poster.svg create mode 100644 src/MovieModal.css create mode 100644 src/MovieModal.jsx diff --git a/.gitignore b/.gitignore index e4d85bc6..cc8ed43f 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,3 @@ dist-ssr *.njsproj *.sln *.sw? -.env diff --git a/public/placeholder-poster.jpg b/public/placeholder-poster.jpg new file mode 100644 index 00000000..e0ed9348 --- /dev/null +++ b/public/placeholder-poster.jpg @@ -0,0 +1 @@ +data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gOTAK/9sAQwADAgIDAgIDAwMDBAMDBAUIBQUEBAUKBwcGCAwKDAwLCgsLDQ4SEA0OEQ4LCxAWEBETFBUVFQwPFxgWFBgSFBUU/9sAQwEDBAQFBAUJBQUJFA0LDRQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU/8AAEQgBLAEsAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A+t6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii diff --git a/public/placeholder-poster.svg b/public/placeholder-poster.svg new file mode 100644 index 00000000..118d5de2 --- /dev/null +++ b/public/placeholder-poster.svg @@ -0,0 +1,7 @@ + + + No Image Available + Movie Poster + + + diff --git a/src/App.jsx b/src/App.jsx index 19690817..63d0a427 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -13,6 +13,11 @@ const App = () => { setView('search'); }; + const handleClearSearch = () => { + setSearchQuery(''); + setView('nowPlaying'); + }; + const handleViewToggle = (selectedView) => { setView(selectedView); }; @@ -21,7 +26,7 @@ const App = () => {

Flixster

- +
{ - +const MovieCard = ({ movie, onClick }) => { const posterBaseUrl = "https://image.tmdb.org/t/p/w500"; + const handleClick = () => { + onClick(movie); + }; + return ( -
+
{`${movie.title} diff --git a/src/MovieList.jsx b/src/MovieList.jsx index a1de87d0..40b2a5d6 100644 --- a/src/MovieList.jsx +++ b/src/MovieList.jsx @@ -1,15 +1,15 @@ import { useState, useEffect } from 'react'; import MovieCard from './MovieCard'; +import MovieModal from './MovieModal'; import './MovieList.css'; const MovieList = ({ searchQuery, view, onViewToggle }) => { + const [selectedMovie, setSelectedMovie] = useState(null); const [movies, setMovies] = useState([]); const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); - const [searchResults, setSearchResults] = useState([]); const fetchMovies = async (pageNum = 1) => { @@ -86,7 +86,7 @@ const MovieList = ({ searchQuery, view, onViewToggle }) => { } }; - // initial fetch of now playing movies + // initial fetch of useEffect(() => { if (view === 'nowPlaying') { fetchMovies(1); @@ -104,6 +104,14 @@ const MovieList = ({ searchQuery, view, onViewToggle }) => { const displayedMovies = view === 'nowPlaying' ? movies : searchResults; const title = view === 'nowPlaying' ? 'Now Playing' : `Search Results for "${searchQuery}"`; + const handleMovieClick = (movie) => { + setSelectedMovie(movie); + }; + + const handleCloseModal = () => { + setSelectedMovie(null); + }; + return (
@@ -129,7 +137,11 @@ const MovieList = ({ searchQuery, view, onViewToggle }) => { ) : (
{displayedMovies.map(movie => ( - + ))}
)} @@ -141,6 +153,13 @@ const MovieList = ({ searchQuery, view, onViewToggle }) => {
)} + + {selectedMovie && ( + + )}
); }; diff --git a/src/MovieModal.css b/src/MovieModal.css new file mode 100644 index 00000000..42550d57 --- /dev/null +++ b/src/MovieModal.css @@ -0,0 +1,173 @@ +/* MODAL OVERLAY */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + overflow-y: auto; + padding: 20px; +} + +/* MAIN MODAL */ +.modal-content { + + position: relative; + height: 80vh; + width: 100%; + max-width: 800px; + max-height: 100vh; + background-color: #1a1a1a; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + display: flex; + flex-direction: column; + + /* netflix-type popup animation */ + animation: modalFadeIn 1s ease-out; +} + +@keyframes modalFadeIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.modal-close { + position: absolute; + top: 10px; + right: 10px; + background: rgba(0, 0, 0, 0.6); + color: white; + border: none; + border-radius: 50%; + width: 30px; + height: 30px; + font-size: 20px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + z-index: 10; + transition: background-color 0.2s; +} + +.modal-close:hover { + background-color: rgba(250, 82, 82, 0.8); +} + +.modal-backdrop { + height: 50%; + background-size: cover; + background-position: center; + position: relative; +} + +.modal-backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(to bottom, rgba(26, 26, 26, 0.2), rgba(26, 26, 26, 1)); +} + +.modal-details { + padding: 20px; + overflow-y: auto; + height: 50%; +} + +.modal-title { + font-size: 24px; + margin-bottom: 15px; + color: #fa5252; +} + +.modal-info { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 15px; + margin-bottom: 20px; +} + +.modal-info-item { + display: flex; + flex-direction: column; +} + +.info-label { + font-size: 14px; + color: #aaa; + margin-bottom: 5px; +} + +.info-value { + font-size: 16px; + color: white; +} + +.modal-vote { + background-color: #fa5252; + color: black; + padding: 3px 8px; + border-radius: 4px; + font-weight: bold; + margin-right: 8px; +} + +.modal-vote-count { + color: #aaa; + font-size: 14px; +} + +.modal-overview h3 { + font-size: 18px; + margin-bottom: 10px; + color: #fa5252; +} + +.modal-overview p { + line-height: 1.6; + color: #ddd; +} + +.modal-loading, .modal-error { + padding: 40px; + text-align: center; + color: white; +} + +.modal-error { + color: #fa5252; +} + +/* Responsive adjustments */ +@media (max-width: 600px) { + .modal-content { + max-height: 95vh; + } + + .modal-backdrop { + height: 180px; + } + + .modal-info { + grid-template-columns: 1fr; + } + + .modal-title { + font-size: 20px; + } +} diff --git a/src/MovieModal.jsx b/src/MovieModal.jsx new file mode 100644 index 00000000..d688d38f --- /dev/null +++ b/src/MovieModal.jsx @@ -0,0 +1,112 @@ +import { useEffect, useState } from 'react'; +import './MovieModal.css'; + +const MovieModal = ({ movie, onClose }) => { + const [movieDetails, setMovieDetails] = useState(null); + + useEffect(() => { + const fetchMovieDetails = async () => { + if (!movie) return; + + const apiKey = import.meta.env.VITE_API_KEY; + const url = `https://api.themoviedb.org/3/movie/${movie.id}?api_key=${apiKey}&language=en-US`; + + const response = await fetch(url); + const data = await response.json(); + setMovieDetails(data); + }; + + fetchMovieDetails(); + }, [movie]); + + useEffect(() => { + const handleEscapeKey = (event) => { + if (event.key === 'Escape') { + onClose(); + } + }; + + const handleClickOutside = (event) => { + if (event.target.classList.contains('modal-overlay')) { + onClose(); + } + }; + + document.addEventListener('keydown', handleEscapeKey); + document.addEventListener('click', handleClickOutside); + + return () => { + document.removeEventListener('keydown', handleEscapeKey); + document.removeEventListener('click', handleClickOutside); + }; + }, [onClose]); + + if (!movie || !movieDetails) return null; + + const movieModalComponent = () => { + return( +
+ + +
+
+
+ +
+

{movieDetails.title}

+ +
+
+ Release Date: + + {new Date(movieDetails.release_date).toLocaleDateString()} + +
+ +
+ Runtime: + + {movieDetails.runtime} minutes + +
+ +
+ Genres: + + {movieDetails.genres.map(genre => genre.name).join(', ')} + +
+ +
+ Rating: + + {movieDetails.vote_average.toFixed(1)} + ({movieDetails.vote_count} votes) + +
+
+ +
+

Overview

+

{movieDetails.overview}

+
+
+
+ ) + } + return ( +
+ {movieModalComponent()} + +
+ ); +}; + +export default MovieModal; diff --git a/src/SearchForm.jsx b/src/SearchForm.jsx index d6cb4aa8..35b7403c 100644 --- a/src/SearchForm.jsx +++ b/src/SearchForm.jsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import './SearchForm.css'; -const SearchForm = ({ onSearch }) => { +const SearchForm = ({ onSearch, onClear }) => { const [query, setQuery] = useState(''); const handleSubmit = (e) => { @@ -14,6 +14,7 @@ const SearchForm = ({ onSearch }) => { const handleClear = (e) => { e.preventDefault(); setQuery(''); + onClear(); }; return ( From 5cd679cbca1c46f32fa910948d6ef6f8690778d2 Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Tue, 10 Jun 2025 19:08:12 -0700 Subject: [PATCH 08/19] chore: add .env to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index cc8ed43f..e4d85bc6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ dist-ssr *.njsproj *.sln *.sw? +.env From 402ac4c521d976aa18b7e0c1bb7f6b4bf7f8dfc7 Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Tue, 10 Jun 2025 19:26:40 -0700 Subject: [PATCH 09/19] style: tweaked modal info grid arrangement, converted px units to rem --- src/MovieModal.css | 54 +++++++++++++++++++++++++--------------------- src/MovieModal.jsx | 2 +- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/MovieModal.css b/src/MovieModal.css index 42550d57..2c4278f8 100644 --- a/src/MovieModal.css +++ b/src/MovieModal.css @@ -11,7 +11,7 @@ align-items: center; z-index: 1000; overflow-y: auto; - padding: 20px; + padding: 2rem; } /* MAIN MODAL */ @@ -46,15 +46,15 @@ .modal-close { position: absolute; - top: 10px; - right: 10px; + top: 1rem; + right: 1rem; background: rgba(0, 0, 0, 0.6); color: white; border: none; border-radius: 50%; - width: 30px; - height: 30px; - font-size: 20px; + width: 3rem; + height: 3rem; + font-size: 2rem; display: flex; align-items: center; justify-content: center; @@ -84,22 +84,23 @@ } .modal-details { - padding: 20px; + padding: 2rem; overflow-y: auto; height: 50%; + /* z-index: 10000000; */ } .modal-title { - font-size: 24px; - margin-bottom: 15px; + font-size: 2.4rem; + margin-bottom: 1.5rem; color: #fa5252; } .modal-info { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 15px; - margin-bottom: 20px; + gap: 1.5rem; + margin-bottom: 6rem; } .modal-info-item { @@ -107,34 +108,39 @@ flex-direction: column; } +.rating +{ + grid-column: 2/3; +} + .info-label { - font-size: 14px; + font-size: 1.4rem; color: #aaa; - margin-bottom: 5px; + margin-bottom: 0.5rem; } .info-value { - font-size: 16px; + font-size: 1.6rem; color: white; } .modal-vote { background-color: #fa5252; color: black; - padding: 3px 8px; - border-radius: 4px; + padding: 0.2rem 0.4rem; + border-radius: 0.4rem; font-weight: bold; - margin-right: 8px; + margin-right: 0.8rem; } .modal-vote-count { color: #aaa; - font-size: 14px; + font-size: 1.4rem; } .modal-overview h3 { - font-size: 18px; - margin-bottom: 10px; + font-size: 1.8rem; + margin-bottom: 1rem; color: #fa5252; } @@ -144,7 +150,7 @@ } .modal-loading, .modal-error { - padding: 40px; + padding: 4rem; text-align: center; color: white; } @@ -154,13 +160,13 @@ } /* Responsive adjustments */ -@media (max-width: 600px) { +@media (max-width: 60rem) { .modal-content { max-height: 95vh; } .modal-backdrop { - height: 180px; + height: 18rem; } .modal-info { @@ -168,6 +174,6 @@ } .modal-title { - font-size: 20px; + font-size: 2rem; } } diff --git a/src/MovieModal.jsx b/src/MovieModal.jsx index d688d38f..09ac9d4c 100644 --- a/src/MovieModal.jsx +++ b/src/MovieModal.jsx @@ -84,7 +84,7 @@ const MovieModal = ({ movie, onClose }) => {
-
+
Rating: {movieDetails.vote_average.toFixed(1)} From 656266e3ec5f1b80d6cd7ab5790b3803232d291d Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Wed, 11 Jun 2025 05:55:34 -0700 Subject: [PATCH 10/19] feat: Implemented drop-down menu and functionality --- src/App.css | 16 ++++++++++++ src/App.jsx | 19 +++++++++----- src/MovieList.css | 2 +- src/MovieList.jsx | 24 ++++++++++++++--- src/SortForm.css | 55 ++++++++++++++++++++++++++++++++++----- src/SortForm.jsx | 65 +++++++++++++++++++++++++++++++++++++++-------- 6 files changed, 154 insertions(+), 27 deletions(-) diff --git a/src/App.css b/src/App.css index 4b8b7db2..b6bc817e 100644 --- a/src/App.css +++ b/src/App.css @@ -17,6 +17,22 @@ z-index: 1000; } +.App-footer { + background-color: #282c34; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + color: white; + padding: 1.5rem; + + /* position: fixed; */ + width: 100%; + bottom: 0; + right: 0; + z-index: 1000; +} + @media (max-width: 900px) { .movie-card { width: 100%; diff --git a/src/App.jsx b/src/App.jsx index 63d0a427..3d30c539 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -7,6 +7,7 @@ import SortForm from './SortForm' const App = () => { const [searchQuery, setSearchQuery] = useState(''); const [view, setView] = useState('nowPlaying'); // 'nowPlaying' or 'search' + const [sortBy, setSortBy] = useState('default'); const handleSearch = (query) => { setSearchQuery(query); @@ -22,18 +23,24 @@ const App = () => { setView(selectedView); }; + const handleSortChange = (sortOption) => { + setSortBy(sortOption); + }; + return (

Flixster

- +
- + + + + +
+

Copyright © 2025 Flixster

+
) } diff --git a/src/MovieList.css b/src/MovieList.css index ebbb09fe..450e8555 100644 --- a/src/MovieList.css +++ b/src/MovieList.css @@ -1,5 +1,5 @@ .movie-list-container { - padding: 11rem 3.5rem; + padding: 11rem 3.5rem 4rem 3.5rem; } .view-toggle { diff --git a/src/MovieList.jsx b/src/MovieList.jsx index 40b2a5d6..4332a027 100644 --- a/src/MovieList.jsx +++ b/src/MovieList.jsx @@ -3,7 +3,7 @@ import MovieCard from './MovieCard'; import MovieModal from './MovieModal'; import './MovieList.css'; -const MovieList = ({ searchQuery, view, onViewToggle }) => { +const MovieList = ({ searchQuery, view, sortBy, onViewToggle }) => { const [selectedMovie, setSelectedMovie] = useState(null); const [movies, setMovies] = useState([]); const [loading, setLoading] = useState(true); @@ -86,7 +86,6 @@ const MovieList = ({ searchQuery, view, onViewToggle }) => { } }; - // initial fetch of useEffect(() => { if (view === 'nowPlaying') { fetchMovies(1); @@ -101,7 +100,26 @@ const MovieList = ({ searchQuery, view, onViewToggle }) => { return
{error}
; } - const displayedMovies = view === 'nowPlaying' ? movies : searchResults; + // sort the movies based on the selected sort option + const sortMovies = (moviesToSort) => { + if (!moviesToSort || moviesToSort.length === 0) return []; + + const sortedMovies = [...moviesToSort]; + + switch (sortBy) { + case 'title': + return sortedMovies.sort((a, b) => a.title.localeCompare(b.title)); + case 'release_date': + return sortedMovies.sort((a, b) => new Date(b.release_date) - new Date(a.release_date)); + case 'vote_average': + return sortedMovies.sort((a, b) => b.vote_average - a.vote_average); + default: + return sortedMovies; + } + }; + + const unsortedMovies = view === 'nowPlaying' ? movies : searchResults; + const displayedMovies = sortMovies(unsortedMovies); const title = view === 'nowPlaying' ? 'Now Playing' : `Search Results for "${searchQuery}"`; const handleMovieClick = (movie) => { diff --git a/src/SortForm.css b/src/SortForm.css index dd8e92ad..99235ee3 100644 --- a/src/SortForm.css +++ b/src/SortForm.css @@ -1,16 +1,59 @@ -.sort-button +.sort-form { + position: relative; +} -{ - height: 4.05rem; +.sort-dropdown { + position: relative; + display: inline-block; +} +.sort-button { + height: 4.05rem; border: 0.1rem solid rgba(250, 82, 82, 0.6); border-radius: 1rem; - - background-color: #1a1e21; color: rgb(250, 82, 82, 0.8); padding: 0 3.5rem; - cursor: pointer; place-content: center; + white-space: nowrap; + min-width: 200px; + text-align: center; +} + +.sort-button:hover { + background-color: #2a2e31; +} + +.sort-options { + position: absolute; + top: 100%; + left: 0; + right: 0; + z-index: 10; + margin-top: 0.5rem; + padding: 0.5rem 0; + background-color: #1a1e21; + border: 0.1rem solid rgba(250, 82, 82, 0.6); + border-radius: 0.5rem; + list-style: none; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + max-height: 300px; + overflow-y: auto; +} + +.sort-option { + padding: 0.8rem 1.2rem; + cursor: pointer; + color: #fff; + transition: background-color 0.2s; +} + +.sort-option:hover { + background-color: #2a2e31; +} + +.sort-option.selected { + background-color: rgba(250, 82, 82, 0.2); + color: #fa5252; } diff --git a/src/SortForm.jsx b/src/SortForm.jsx index 60d6e725..4ab13915 100644 --- a/src/SortForm.jsx +++ b/src/SortForm.jsx @@ -1,14 +1,57 @@ -import './SortForm.css' - -const SortForm = () => - { - return ( -
- + + {isOpen && ( +
    + {sortOptions.map(option => ( +
  • handleOptionClick(option.id)} + role="option" + aria-selected={selectedSort === option.id} + > + {option.label} +
  • + ))} +
+ )}
- ) - } +
+ ); +}; -export default SortForm +export default SortForm; From 40afa377550e2699120167ba29c6206ffe3e737c Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Wed, 11 Jun 2025 08:00:24 -0700 Subject: [PATCH 11/19] feat: add favorite/watched toggles and sidebar navigation with filtering - Added heart and eye icons to each movie tile for favoriting and marking as watched - Toggled visual state changes on icon clicks (filled vs outlined) - Implemented state management for favorites and watched lists - Built sidebar with navigation to Home, Favorites, and Watched pages - Each view filters and displays the appropriate movie set in grid layout --- src/App.css | 27 +++++++++---- src/App.jsx | 98 ++++++++++++++++++++++++++++++++++++++++++++--- src/MovieCard.css | 61 ++++++++++++++++++++++++++++- src/MovieCard.jsx | 87 +++++++++++++++++++++++++++++++++++------ src/MovieList.jsx | 88 ++++++++++++++++++++++++++++++++---------- src/SideBar.css | 62 ++++++++++++++++++++++++++++++ src/SideBar.jsx | 28 ++++++++++++++ 7 files changed, 404 insertions(+), 47 deletions(-) create mode 100644 src/SideBar.css create mode 100644 src/SideBar.jsx diff --git a/src/App.css b/src/App.css index b6bc817e..4882adbe 100644 --- a/src/App.css +++ b/src/App.css @@ -1,6 +1,8 @@ .App { text-align: center; - /* width: 70%; */ + display: flex; + flex-direction: column; + min-height: 100vh; } .App-header { @@ -11,12 +13,18 @@ justify-content: space-between; color: white; padding: 1.5rem; - position: fixed; width: 100%; z-index: 1000; } +.App-main { + flex: 1; + margin-left: 200px; /* Match sidebar width */ + padding-top: 80px; /* Match header height */ + transition: margin-left 0.3s ease; +} + .App-footer { background-color: #282c34; display: flex; @@ -25,12 +33,8 @@ justify-content: center; color: white; padding: 1.5rem; - - /* position: fixed; */ width: 100%; - bottom: 0; - right: 0; - z-index: 1000; + margin-left: 200px; /* Match sidebar width */ } @media (max-width: 900px) { @@ -54,3 +58,12 @@ justify-content: center; } } +@media (max-width: 768px) { + .App-main { + margin-left: 60px; /* Match collapsed sidebar width */ + } + + .App-footer { + margin-left: 60px; /* Match collapsed sidebar width */ + } +} diff --git a/src/App.jsx b/src/App.jsx index 3d30c539..d046ad5d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,13 +1,40 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import './App.css' import MovieList from './MovieList' import SearchForm from './SearchForm' import SortForm from './SortForm' +import SideBar from './SideBar' const App = () => { const [searchQuery, setSearchQuery] = useState(''); const [view, setView] = useState('nowPlaying'); // 'nowPlaying' or 'search' const [sortBy, setSortBy] = useState('default'); + const [currentPage, setCurrentPage] = useState('home'); + const [favoriteMovies, setFavoriteMovies] = useState([]); + const [watchedMovies, setWatchedMovies] = useState([]); + + // Load favorites and watched movies from localStorage on initial render + useEffect(() => { + const storedFavorites = localStorage.getItem('favoriteMovies'); + const storedWatched = localStorage.getItem('watchedMovies'); + + if (storedFavorites) { + setFavoriteMovies(JSON.parse(storedFavorites)); + } + + if (storedWatched) { + setWatchedMovies(JSON.parse(storedWatched)); + } + }, []); + + // Save to localStorage whenever favorites or watched movies change + useEffect(() => { + localStorage.setItem('favoriteMovies', JSON.stringify(favoriteMovies)); + }, [favoriteMovies]); + + useEffect(() => { + localStorage.setItem('watchedMovies', JSON.stringify(watchedMovies)); + }, [watchedMovies]); const handleSearch = (query) => { setSearchQuery(query); @@ -27,17 +54,76 @@ const App = () => { setSortBy(sortOption); }; + const handlePageChange = (page) => { + setCurrentPage(page); + // Reset to nowPlaying view when changing pages + if (page === 'home') { + setView('nowPlaying'); + } + }; + + const toggleFavorite = (movie) => { + setFavoriteMovies(prevFavorites => { + const isAlreadyFavorite = prevFavorites.some(favMovie => favMovie.id === movie.id); + + if (isAlreadyFavorite) { + return prevFavorites.filter(favMovie => favMovie.id !== movie.id); + } else { + return [...prevFavorites, movie]; + } + }); + }; + + const toggleWatched = (movie) => { + setWatchedMovies(prevWatched => { + const isAlreadyWatched = prevWatched.some(watchedMovie => watchedMovie.id === movie.id); + + if (isAlreadyWatched) { + return prevWatched.filter(watchedMovie => watchedMovie.id !== movie.id); + } else { + return [...prevWatched, movie]; + } + }); + }; + + const isMovieFavorite = (movieId) => { + return favoriteMovies.some(movie => movie.id === movieId); + }; + + const isMovieWatched = (movieId) => { + return watchedMovies.some(movie => movie.id === movieId); + }; + return (

Flixster

- - + {currentPage === 'home' && ( + <> + + + + )}
- - - + + +
+ +
+

Copyright © 2025 Flixster

diff --git a/src/MovieCard.css b/src/MovieCard.css index 0a0b7563..603190b8 100644 --- a/src/MovieCard.css +++ b/src/MovieCard.css @@ -6,16 +6,73 @@ transition: transform 0.3s ease, box-shadow 0.3s ease; background-color: #1a1a1a; - cursor: pointer; - /* z-index: */ + position: relative; } .movie-card:hover { transform: scale(1.03); box-shadow: 0rem 0.15rem 0.9rem rgba(229, 9, 20); +} + +.movie-card-content { + cursor: pointer; +} + +.movie-card-actions { + position: absolute; + top: 10px; + right: 10px; + display: flex; + flex-direction: column; + gap: 8px; + z-index: 10; +} + +.action-button { + width: 32px; + height: 32px; + border-radius: 50%; + border: none; + background-color: rgba(0, 0, 0, 0.6); + color: white; + font-size: 18px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; +} +.action-button:hover { + background-color: rgba(0, 0, 0, 0.8); + transform: scale(1.1); } +.favorite-button.active { + background-color: #fa5252; + color: white; +} + +.watched-button.active { + background-color: #40c057; + color: white; +} + +.favorite-button svg, +.watched-button svg { + transition: fill 0.3s ease, stroke 0.3s ease; +} + +/* Already in your CSS — controls bg */ +.favorite-button.active { + background-color: rgba(250, 82, 82, 0.2); +} + +.watched-button.active { + background-color: rgba(64, 192, 87, 0.2); +} + + .movie-poster { width: 100%; height: 300px; diff --git a/src/MovieCard.jsx b/src/MovieCard.jsx index 89cee098..189199ea 100644 --- a/src/MovieCard.jsx +++ b/src/MovieCard.jsx @@ -1,23 +1,88 @@ import './MovieCard.css'; -const MovieCard = ({ movie, onClick }) => { +const MovieCard = ({ + movie, + onClick, + onToggleFavorite, + onToggleWatched, + isFavorite, + isWatched +}) => { const posterBaseUrl = "https://image.tmdb.org/t/p/w500"; const handleClick = () => { onClick(movie); }; + const handleFavoriteClick = (e) => { + e.stopPropagation(); + onToggleFavorite(movie); + }; + + const handleWatchedClick = (e) => { + e.stopPropagation(); + onToggleWatched(movie); + }; + return ( -
- {`${movie.title} -
-

{movie.title}

-
- {movie.vote_average.toFixed(1)} +
+
+ + + + +
+ +
+ {`${movie.title} +
+

{movie.title}

+
+ {movie.vote_average.toFixed(1)} +
diff --git a/src/MovieList.jsx b/src/MovieList.jsx index 4332a027..06cc6849 100644 --- a/src/MovieList.jsx +++ b/src/MovieList.jsx @@ -3,7 +3,19 @@ import MovieCard from './MovieCard'; import MovieModal from './MovieModal'; import './MovieList.css'; -const MovieList = ({ searchQuery, view, sortBy, onViewToggle }) => { +const MovieList = ({ + searchQuery, + view, + sortBy, + onViewToggle, + currentPage, + favoriteMovies, + watchedMovies, + onToggleFavorite, + onToggleWatched, + isMovieFavorite, + isMovieWatched +}) => { const [selectedMovie, setSelectedMovie] = useState(null); const [movies, setMovies] = useState([]); const [loading, setLoading] = useState(true); @@ -118,9 +130,31 @@ const MovieList = ({ searchQuery, view, sortBy, onViewToggle }) => { } }; - const unsortedMovies = view === 'nowPlaying' ? movies : searchResults; - const displayedMovies = sortMovies(unsortedMovies); - const title = view === 'nowPlaying' ? 'Now Playing' : `Search Results for "${searchQuery}"`; + // Determine which movies to display based on the current page + const getMoviesToDisplay = () => { + if (currentPage === 'favorites') { + return sortMovies(favoriteMovies); + } else if (currentPage === 'watched') { + return sortMovies(watchedMovies); + } else { + // Home page + return sortMovies(view === 'nowPlaying' ? movies : searchResults); + } + }; + + // Get the title for the current page + const getPageTitle = () => { + if (currentPage === 'favorites') { + return 'My Favorites'; + } else if (currentPage === 'watched') { + return 'Watched Movies'; + } else { + return view === 'nowPlaying' ? 'Now Playing' : `Search Results for "${searchQuery}"`; + } + }; + + const displayedMovies = getMoviesToDisplay(); + const title = getPageTitle(); const handleMovieClick = (movie) => { setSelectedMovie(movie); @@ -132,26 +166,34 @@ const MovieList = ({ searchQuery, view, sortBy, onViewToggle }) => { return (
-
- - -
+ {currentPage === 'home' && ( +
+ + +
+ )}

{title}

{displayedMovies.length === 0 && !loading ? ( -
No movies found
+
+ {currentPage === 'favorites' + ? "You haven't added any favorites yet" + : currentPage === 'watched' + ? "You haven't marked any movies as watched yet" + : "No movies found"} +
) : (
{displayedMovies.map(movie => ( @@ -159,12 +201,16 @@ const MovieList = ({ searchQuery, view, sortBy, onViewToggle }) => { key={movie.id} movie={movie} onClick={handleMovieClick} + onToggleFavorite={() => onToggleFavorite(movie)} + onToggleWatched={() => onToggleWatched(movie)} + isFavorite={isMovieFavorite(movie.id)} + isWatched={isMovieWatched(movie.id)} /> ))}
)} - {view === 'nowPlaying' && hasMore && !loading && ( + {currentPage === 'home' && view === 'nowPlaying' && hasMore && !loading && (
+ + ))} + + + ); +} + +export default SideBar From ebffc7e7749b053e6d00991b2b2d5a0745b6bf4c Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Wed, 11 Jun 2025 10:07:30 -0700 Subject: [PATCH 12/19] style: Updated liked/watched icons and edited sidebar styling --- src/MovieList.jsx | 10 +++++----- src/MovieModal.jsx | 7 +++++++ src/SideBar.css | 2 +- src/SideBar.jsx | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/MovieList.jsx b/src/MovieList.jsx index 06cc6849..aa851ebc 100644 --- a/src/MovieList.jsx +++ b/src/MovieList.jsx @@ -130,7 +130,7 @@ const MovieList = ({ } }; - // Determine which movies to display based on the current page + const getMoviesToDisplay = () => { if (currentPage === 'favorites') { return sortMovies(favoriteMovies); @@ -142,7 +142,7 @@ const MovieList = ({ } }; - // Get the title for the current page + const getPageTitle = () => { if (currentPage === 'favorites') { return 'My Favorites'; @@ -189,10 +189,10 @@ const MovieList = ({ {displayedMovies.length === 0 && !loading ? (
{currentPage === 'favorites' - ? "You haven't added any favorites yet" + ? "you havent added any favorites yet" : currentPage === 'watched' - ? "You haven't marked any movies as watched yet" - : "No movies found"} + ? "you haven't marked any movies as watched yet" + : "no movies found"}
) : (
diff --git a/src/MovieModal.jsx b/src/MovieModal.jsx index 09ac9d4c..65898e36 100644 --- a/src/MovieModal.jsx +++ b/src/MovieModal.jsx @@ -98,6 +98,13 @@ const MovieModal = ({ movie, onClose }) => {

{movieDetails.overview}

+ +
+
+ +
+ +
) } diff --git a/src/SideBar.css b/src/SideBar.css index d4a6d5e1..375a9159 100644 --- a/src/SideBar.css +++ b/src/SideBar.css @@ -7,7 +7,7 @@ width: 200px; left: 0; top: 0; - padding-top: 80px; + padding-top: 10rem; box-shadow: 2px 0 5px rgba(0, 0, 0, 0.3); z-index: 900; } diff --git a/src/SideBar.jsx b/src/SideBar.jsx index 3df2330a..09538850 100644 --- a/src/SideBar.jsx +++ b/src/SideBar.jsx @@ -1,4 +1,4 @@ -import './SideBar.css'; +import 'SideBar.css'; const SideBar = ({ currentPage, onPageChange }) => { const pages = [ From 594f8a59205f61eb8fb073a2d3a45f13e75f948a Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Wed, 11 Jun 2025 10:12:05 -0700 Subject: [PATCH 13/19] bugfix: connected sidebar.css --- src/SideBar.css | 14 +++++++------- src/SideBar.jsx | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/SideBar.css b/src/SideBar.css index 375a9159..5544ddb9 100644 --- a/src/SideBar.css +++ b/src/SideBar.css @@ -4,7 +4,7 @@ background-color: #1a1a1a; position: fixed; height: 100vh; - width: 200px; + width: 20rem; left: 0; top: 0; padding-top: 10rem; @@ -21,13 +21,13 @@ .side-bar-item { width: 100%; - margin-bottom: 10px; + margin-bottom: 1rem; } .side-bar-link { display: block; width: 100%; - padding: 15px 20px; + padding: 1.5rem 2rem; color: #fff; text-decoration: none; font-size: 1.1rem; @@ -46,16 +46,16 @@ .side-bar-link.active { background-color: #fa5252; color: #fff; - border-left: 4px solid #fff; + border-left: 4rem solid #fff; } -@media (max-width: 768px) { +@media (max-width: 76.8rem) { .side-bar { - width: 60px; + width: 6rem; } .side-bar-link { - padding: 15px 5px; + padding: 15rem 0.5rem; text-align: center; font-size: 0.8rem; } diff --git a/src/SideBar.jsx b/src/SideBar.jsx index 09538850..3df2330a 100644 --- a/src/SideBar.jsx +++ b/src/SideBar.jsx @@ -1,4 +1,4 @@ -import 'SideBar.css'; +import './SideBar.css'; const SideBar = ({ currentPage, onPageChange }) => { const pages = [ From 420d182c5685bcf50d85152c0262c37d7165bd7a Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Wed, 11 Jun 2025 12:47:02 -0700 Subject: [PATCH 14/19] feat: added modal thumbnails for video preview style: added design finishing touches --- src/App.css | 9 ++-- src/App.jsx | 1 + src/MovieCard.css | 1 - src/MovieList.css | 2 +- src/MovieModal.css | 75 +++++++++++++++++++++++++++++++-- src/MovieModal.jsx | 103 ++++++++++++++++++++++++++++++++++++++------- src/SearchForm.css | 5 +++ src/SideBar.css | 2 +- 8 files changed, 172 insertions(+), 26 deletions(-) diff --git a/src/App.css b/src/App.css index 4882adbe..c5fc8a43 100644 --- a/src/App.css +++ b/src/App.css @@ -20,8 +20,8 @@ .App-main { flex: 1; - margin-left: 200px; /* Match sidebar width */ - padding-top: 80px; /* Match header height */ + margin-left: 18rem; + padding-top: 8rem; transition: margin-left 0.3s ease; } @@ -34,7 +34,10 @@ color: white; padding: 1.5rem; width: 100%; - margin-left: 200px; /* Match sidebar width */ + margin-left: 18rem; + + + text-align: center; } @media (max-width: 900px) { diff --git a/src/App.jsx b/src/App.jsx index d046ad5d..e45b43f3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -131,4 +131,5 @@ const App = () => { ) } + export default App diff --git a/src/MovieCard.css b/src/MovieCard.css index 603190b8..b28af70b 100644 --- a/src/MovieCard.css +++ b/src/MovieCard.css @@ -63,7 +63,6 @@ transition: fill 0.3s ease, stroke 0.3s ease; } -/* Already in your CSS — controls bg */ .favorite-button.active { background-color: rgba(250, 82, 82, 0.2); } diff --git a/src/MovieList.css b/src/MovieList.css index 450e8555..d64d3251 100644 --- a/src/MovieList.css +++ b/src/MovieList.css @@ -31,7 +31,7 @@ .toggle-button:disabled { opacity: 0.5; - cursor: not-allowed; + cursor: default; } .section-title { diff --git a/src/MovieModal.css b/src/MovieModal.css index 2c4278f8..ebbc44f6 100644 --- a/src/MovieModal.css +++ b/src/MovieModal.css @@ -18,7 +18,7 @@ .modal-content { position: relative; - height: 80vh; + height: 85vh; width: 100%; max-width: 800px; max-height: 100vh; @@ -68,7 +68,7 @@ } .modal-backdrop { - height: 50%; + height: 40%; background-size: cover; background-position: center; position: relative; @@ -86,7 +86,7 @@ .modal-details { padding: 2rem; overflow-y: auto; - height: 50%; + height: 60%; /* z-index: 10000000; */ } @@ -159,7 +159,74 @@ color: #fa5252; } -/* Responsive adjustments */ + +.trailer-thumbnail, .trailer-player +{ + position: relative; +} + +.modal-trailer +{ + width: 50rem; + height: 40rem; + margin: 0 auto; + /* border: 0.1rem solid #fa5252; */ +} + +.trailer-frame +{ + border: 1rem solid #fa5252; +} + +.close-button{ + position: absolute; + top: 1rem; right: 1rem; + background-color: #282c34; + padding : 0.5rem 1rem; + border-radius: 6rem; + + border: none; + color: #fa5252; + cursor: pointer; + font-size: 1.6rem; + font-weight: bold; + transition: background-color 0.3s ease + color 0.3s ease; +} + +.play-button svg:hover +{ + /* background-color: #; */ + background-color: rgba(250, 82, 82, 0.5); + border-radius: 67rem; +} + + + +.play-button +{ + height: 5rem; + width: 5rem; + position: absolute; + top: 40%; + left: 48%; + z-index: 100000; + background-color: rgba(250, 82, 82, 0.2); + border-radius: 67rem; + cursor: pointer; +} + +/* .favorite-button svg, +.watched-button svg { + transition: fill 0.3s ease, stroke 0.3s ease; +} + +.play-button.active { + background-color: rgba(250, 82, 82, 0.2); +} */ + + + @media (max-width: 60rem) { .modal-content { max-height: 95vh; diff --git a/src/MovieModal.jsx b/src/MovieModal.jsx index 65898e36..ae309848 100644 --- a/src/MovieModal.jsx +++ b/src/MovieModal.jsx @@ -3,17 +3,48 @@ import './MovieModal.css'; const MovieModal = ({ movie, onClose }) => { const [movieDetails, setMovieDetails] = useState(null); + const [trailers, setTrailers] = useState([]); + const [selectedTrailer, setSelectedTrailer] = useState(null); + const [showTrailer, setShowTrailer] = useState(false); + const [loading, setLoading] = useState(true); useEffect(() => { const fetchMovieDetails = async () => { if (!movie) return; + setLoading(true); const apiKey = import.meta.env.VITE_API_KEY; - const url = `https://api.themoviedb.org/3/movie/${movie.id}?api_key=${apiKey}&language=en-US`; - - const response = await fetch(url); - const data = await response.json(); - setMovieDetails(data); + const detailsUrl = `https://api.themoviedb.org/3/movie/${movie.id}?api_key=${apiKey}&language=en-US`; + const videosUrl = `https://api.themoviedb.org/3/movie/${movie.id}/videos?api_key=${apiKey}&language=en-US`; + + try { + // Fetch movie details + const detailsResponse = await fetch(detailsUrl); + const detailsData = await detailsResponse.json(); + setMovieDetails(detailsData); + + // Fetch movie videos (trailers) + const videosResponse = await fetch(videosUrl); + const videosData = await videosResponse.json(); + + // Filter for YouTube trailers + const youtubeTrailers = videosData.results.filter( + video => video.site === 'YouTube' && + (video.type === 'Trailer' || video.type === 'Teaser') + ); + + setTrailers(youtubeTrailers); + + // Set the first trailer as selected if available + if (youtubeTrailers.length > 0) { + setSelectedTrailer(youtubeTrailers[0]); + } + + setLoading(false); + } catch (error) { + console.error('Error fetching movie data:', error); + setLoading(false); + } }; fetchMovieDetails(); @@ -41,12 +72,26 @@ const MovieModal = ({ movie, onClose }) => { }; }, [onClose]); - if (!movie || !movieDetails) return null; + const handlePlayTrailer = () => { + setShowTrailer(true); + }; + + const handleCloseTrailer = () => { + setShowTrailer(false); + }; + + if (!movie || !movieDetails || loading) return ( +
+
+
Loading movie details...
+
+
+ ); const movieModalComponent = () => { - return( -
- + return ( +
+
{

Overview

{movieDetails.overview}

-
-
-
- + {trailers.length > 0 && ( +
+

Trailer

+ {showTrailer ? ( +
+ + +
+ ) : ( +
+ Trailer thumbnail +
+ + + +
+
+ )}
- + )}
- ) - } + ); + }; return (
{movieModalComponent()} diff --git a/src/SearchForm.css b/src/SearchForm.css index 080439d0..1596992a 100644 --- a/src/SearchForm.css +++ b/src/SearchForm.css @@ -36,6 +36,11 @@ /* background-color: #868e96; */ } +.search-bar::content +{ + margin-left: 2rem; +} + .search-button { diff --git a/src/SideBar.css b/src/SideBar.css index 5544ddb9..21114b99 100644 --- a/src/SideBar.css +++ b/src/SideBar.css @@ -46,7 +46,7 @@ .side-bar-link.active { background-color: #fa5252; color: #fff; - border-left: 4rem solid #fff; + border-left: 0.1rem solid #fff; } @media (max-width: 76.8rem) { From 53e5c835953d94a4c208e62fed13f2bef4608d12 Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Fri, 13 Jun 2025 11:54:33 -0700 Subject: [PATCH 15/19] style: final changes and formatting --- src/App.css | 56 +++++++++++++++++++++++++++++++++++++++++----- src/App.jsx | 20 ++++++++++------- src/MovieCard.css | 2 +- src/SearchForm.css | 4 +++- src/SideBar.css | 4 ++-- 5 files changed, 68 insertions(+), 18 deletions(-) diff --git a/src/App.css b/src/App.css index c5fc8a43..ad680a95 100644 --- a/src/App.css +++ b/src/App.css @@ -1,8 +1,49 @@ +.header +{ + /* display: ; */ +} + +.title +{ + + background-color: #1a1a1a; + /* background-color: red; */ + z-index: 10000; + height: 70px; + display: flex; + /* flex-direction: row; */ + align-items: center; + justify-content: center; + + /* background-color: #282c34; */ + /* display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; */ + color: white; + /* padding-bottom: 1.5rem: */ + position: fixed; + top: 20; + width: 100%; + z-index: 1000; + + text-transform: uppercase; + /* box-shadow: inset 1px 1px 50px 9px rgba(67, 30, 30, 0.9); */ +} + +.title h1 +{ + font-size: 3.2rem; + font-weight: 700; + letter-spacing: 0.25rem; +} + .App { text-align: center; display: flex; flex-direction: column; min-height: 100vh; + overflow-x: hidden; } .App-header { @@ -10,12 +51,15 @@ display: flex; flex-direction: row; align-items: center; - justify-content: space-between; + /* justify-content: space-between; */ color: white; padding: 1.5rem; position: fixed; + top: 65px; width: 100%; z-index: 1000; + + /* margin-left: 20rem; */ } .App-main { @@ -29,12 +73,12 @@ background-color: #282c34; display: flex; flex-direction: row; - align-items: center; - justify-content: center; + align-items: right; + justify-content: right; color: white; padding: 1.5rem; width: 100%; - margin-left: 18rem; + /* margin-left: 18rem; */ text-align: center; @@ -63,10 +107,10 @@ } @media (max-width: 768px) { .App-main { - margin-left: 60px; /* Match collapsed sidebar width */ + margin-left: 60px; } .App-footer { - margin-left: 60px; /* Match collapsed sidebar width */ + margin-left: 60px; } } diff --git a/src/App.jsx b/src/App.jsx index e45b43f3..4ed2918e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -96,14 +96,18 @@ const App = () => { return (
-
-

Flixster

- {currentPage === 'home' && ( - <> - - - - )} +
+
+

Flixster

+
+ + {currentPage === 'home' && ( + <> + + + + )} +
diff --git a/src/MovieCard.css b/src/MovieCard.css index b28af70b..6d26c76b 100644 --- a/src/MovieCard.css +++ b/src/MovieCard.css @@ -100,7 +100,7 @@ .vote-average { background-color: #fa5252; color: black; - padding: 0.2rem 0.5rem; + padding: 0.5rem 1rem; margin: 0.05rem auto; border-radius: 4px; font-weight: bold; diff --git a/src/SearchForm.css b/src/SearchForm.css index 1596992a..b4d166d2 100644 --- a/src/SearchForm.css +++ b/src/SearchForm.css @@ -8,6 +8,8 @@ { width: 100%; + padding-left: 200px; + padding-right: 200px; display: flex; align-items: center; @@ -21,7 +23,7 @@ border: 0.1rem solid rgba(250, 82, 82, 0.6); border-right: none; - width: 50vw; + width: 30vw; padding: 1rem 1rem; /* margin-right: 2rem; */ diff --git a/src/SideBar.css b/src/SideBar.css index 21114b99..6e695042 100644 --- a/src/SideBar.css +++ b/src/SideBar.css @@ -7,7 +7,7 @@ width: 20rem; left: 0; top: 0; - padding-top: 10rem; + padding-top: 17rem; box-shadow: 2px 0 5px rgba(0, 0, 0, 0.3); z-index: 900; } @@ -40,7 +40,7 @@ .side-bar-link:hover { background-color: #333; - color: #fa5252; + color: rgb(250, 82, 82); } .side-bar-link.active { From b8198a16515b60b194fa2b9fb64adca598b52b82 Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Fri, 13 Jun 2025 15:19:16 -0700 Subject: [PATCH 16/19] Bugfix: moved header to front --- src/App.css | 13 +++++-------- src/SideBar.css | 4 ++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/App.css b/src/App.css index ad680a95..81108acb 100644 --- a/src/App.css +++ b/src/App.css @@ -1,14 +1,10 @@ -.header -{ - /* display: ; */ -} .title { background-color: #1a1a1a; /* background-color: red; */ - z-index: 10000; + /* z-index: 99; */ height: 70px; display: flex; /* flex-direction: row; */ @@ -25,7 +21,7 @@ position: fixed; top: 20; width: 100%; - z-index: 1000; + z-index: 990; text-transform: uppercase; /* box-shadow: inset 1px 1px 50px 9px rgba(67, 30, 30, 0.9); */ @@ -51,15 +47,16 @@ display: flex; flex-direction: row; align-items: center; + /* padding-left: 15rem */ /* justify-content: space-between; */ color: white; padding: 1.5rem; position: fixed; top: 65px; width: 100%; - z-index: 1000; + z-index: 990; - /* margin-left: 20rem; */ + margin-left: 20rem; } .App-main { diff --git a/src/SideBar.css b/src/SideBar.css index 6e695042..9a6592a5 100644 --- a/src/SideBar.css +++ b/src/SideBar.css @@ -7,9 +7,9 @@ width: 20rem; left: 0; top: 0; - padding-top: 17rem; + padding-top: 1rem; box-shadow: 2px 0 5px rgba(0, 0, 0, 0.3); - z-index: 900; + z-index: 999; } .side-bar-nav { From b237211c42823df877a87cfea019f5c4973ec082 Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Fri, 13 Jun 2025 15:30:26 -0700 Subject: [PATCH 17/19] Update README.md Updated README for submission --- README.md | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f768e33f..8bf1c2f2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,140 @@ -# React + Vite +## WEEK 2 : Flixster -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +Submitted by: Jason Chimdinma Jason -Currently, two official plugins are available: +Estimated time spent: 36 hours spent in total -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +Deployed Application (**required**): [Flixster Deployed Site](https://flixster-starter-5ate.onrender.com) + +### Application Features + +#### REQUIRED FEATURES + +- [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. + - [X] The search bar should include: + - [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 + + +- [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. +- [X] **Sidebar** + - [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 + + +`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? + +Yes, the labs provided a strong foundation for building this assignment. Topics like fetching data from APIs, JSX rendering with dynamic props, and React hooks (useState, useEffect) were especially helpful. However, I initially felt unprepared when implementing the modal component for movie details. Managing component state and ensuring proper data flow between the movie card and modal took extra time and effort to figure out. + +* 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. + +Implemented lazy loading for movie images to improve performance. + +Added a search bar and filter dropdown to let users filter movies by genre or rating. + +Improved the mobile responsiveness and optimized how movie cards stack on smaller screens. + +Added a loading spinner or skeleton UI while fetching data, to enhance the user experience. + +* 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? + +The demo went well overall — the app loaded correctly, the movie cards rendered dynamically, and the hover/animation effects added polish. One thing that didn’t go as planned was a slight delay in loading some movie data, which could have been addressed with a loading state. + +One cool thing I noticed during my peer's demo was their use of a favorites feature where users could click a heart to save movies to a favorites list. I’d love to try adding that in the future, possibly with local storage or a simple backend. + +### Open-source libraries used + +-React +-TMDB API + +### 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. +TA - Jesus Perez +Intern Manager - Ameya Gawde +Peer mentors - Adam Chappell, Ishan Balar +Paige & Michelle(Tech Fellows) - for all the vocal and technical support +King Igbozuruike & Angel Balogun & Eniola Farinde - My friends/support system who kept pushing me to go farther and implement as much as i can From bcc4e0a12da4dc2d667169f0892c50d4e0bb4e30 Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Fri, 13 Jun 2025 16:48:51 -0700 Subject: [PATCH 18/19] Added walkthrough video to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8bf1c2f2..3e5ab1e4 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Deployed Application (**required**): [Flixster Deployed Site](https://flixster-s ### Walkthrough Video -`ADD_EMBEDDED_CODE_HERE` +[WALKTHROUGH VIDEO](https://www.loom.com/share/4b1b3fd186ca4d41a64356b68e177e92?sid=9be1bf7f-1f08-4537-9717-029b8728b642) ### Reflection From 8536e3166dc59c7d28eab24cbebe71b1fe0233df Mon Sep 17 00:00:00 2001 From: Jason Jason Date: Wed, 18 Jun 2025 09:24:23 -0700 Subject: [PATCH 19/19] Update src/index.css Co-authored-by: Ameya Gawde --- src/index.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/index.css b/src/index.css index 1580fedf..097d4fbf 100644 --- a/src/index.css +++ b/src/index.css @@ -29,8 +29,6 @@ button:hover { color: white; } - - html { font-size: 62.5%;