Added responsive design classes, moved some things to common folder, added loading states
This commit is contained in:
parent
f1b3d9e5be
commit
81e0e88eaa
|
@ -1,6 +1,5 @@
|
||||||
import { ReactEventHandler, useState } from "react";
|
import { ReactEventHandler, useState } from "react";
|
||||||
|
export default function Pagination({ pages, onPageChange, currentPage }: any) {
|
||||||
export default ({ pages, onPageChange, currentPage }: any) => {
|
|
||||||
const [page, setPage] = useState(currentPage);
|
const [page, setPage] = useState(currentPage);
|
||||||
const handleClick = (e: any) => {
|
const handleClick = (e: any) => {
|
||||||
const { value } = e.target;
|
const { value } = e.target;
|
||||||
|
@ -62,4 +61,4 @@ export default ({ pages, onPageChange, currentPage }: any) => {
|
||||||
<QuickLink value={">>"} />
|
<QuickLink value={">>"} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import {render, screen} from '@testing-library/react'
|
import {render, screen} from '@testing-library/react'
|
||||||
import MovieCard from '../src/app/Search/MovieCard.tsx'
|
import MovieCard from '../src/common/components/MovieCard.tsx'
|
||||||
const movie = {
|
const movie = {
|
||||||
Title: "Dune",
|
Title: "Dune",
|
||||||
Year: "2000",
|
Year: "2000",
|
||||||
|
|
|
@ -1,8 +1,19 @@
|
||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import { queryOMDb } from "../src/api/omdb.ts";
|
import { queryOMDb } from "../src/api/omdb.ts";
|
||||||
|
|
||||||
test("queryOMDb returns movie data", async () => {
|
test("queryOMDb returns movie data successfully", async () => {
|
||||||
const data = await queryOMDb("s=back to");
|
const data = await queryOMDb("s=back to");
|
||||||
|
|
||||||
expect(data).toHaveProperty("Response");
|
expect(data).toHaveProperty("Response", "True");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("queryOMDb returns movie with error", async () => {
|
||||||
|
const data = await queryOMDb("f=dune");
|
||||||
|
|
||||||
|
expect(data).toHaveProperty("Response", "False");
|
||||||
|
});
|
||||||
|
|
||||||
|
// f=dune
|
||||||
|
|
||||||
|
// Warning: React does not recognize the `fetchPriority` prop on a DOM element
|
||||||
|
// The `punycode` module is deprecated. Please use a userland alternative instead.
|
288
movie-search/package-lock.json
generated
288
movie-search/package-lock.json
generated
|
@ -10,18 +10,19 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.11.4",
|
"@emotion/react": "^11.11.4",
|
||||||
"@emotion/styled": "^11.11.5",
|
"@emotion/styled": "^11.11.5",
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
"framer-motion": "^11.2.6",
|
"framer-motion": "^11.2.6",
|
||||||
"next": "14.2.3",
|
"next": "^14.2.3",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"ts-node-dev": "^2.0.0"
|
"ts-node-dev": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/preset-typescript": "^7.24.6",
|
||||||
"@testing-library/jest-dom": "^6.4.5",
|
"@testing-library/jest-dom": "^6.4.5",
|
||||||
"@testing-library/react": "^15.0.7",
|
"@testing-library/react": "^15.0.7",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
|
||||||
"@types/react-dom": "^18",
|
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "14.2.3",
|
"eslint-config-next": "14.2.3",
|
||||||
|
@ -157,6 +158,18 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-annotate-as-pure": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-DitEzDfOMnd13kZnDqns1ccmftwJTS9DMkyn9pYTxulS7bZxUxpMly3Nf23QQ6NwA4UB8lAqjbqWtyvElEMAkg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.24.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-compilation-targets": {
|
"node_modules/@babel/helper-compilation-targets": {
|
||||||
"version": "7.24.6",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz",
|
||||||
|
@ -191,6 +204,38 @@
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-create-class-features-plugin": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-djsosdPJVZE6Vsw3kk7IPRWethP94WHGOhQTc67SNXE0ZzMhHgALw8iGmYS0TD1bbMM0VDROy43od7/hN6WYcA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-annotate-as-pure": "^7.24.6",
|
||||||
|
"@babel/helper-environment-visitor": "^7.24.6",
|
||||||
|
"@babel/helper-function-name": "^7.24.6",
|
||||||
|
"@babel/helper-member-expression-to-functions": "^7.24.6",
|
||||||
|
"@babel/helper-optimise-call-expression": "^7.24.6",
|
||||||
|
"@babel/helper-replace-supers": "^7.24.6",
|
||||||
|
"@babel/helper-skip-transparent-expression-wrappers": "^7.24.6",
|
||||||
|
"@babel/helper-split-export-declaration": "^7.24.6",
|
||||||
|
"semver": "^6.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
|
||||||
|
"version": "6.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
|
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-environment-visitor": {
|
"node_modules/@babel/helper-environment-visitor": {
|
||||||
"version": "7.24.6",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz",
|
||||||
|
@ -225,6 +270,18 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-member-expression-to-functions": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-OTsCufZTxDUsv2/eDXanw/mUZHWOxSbEmC3pP8cgjcy5rgeVPWWMStnv274DV60JtHxTk0adT0QrCzC4M9NWGg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.24.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-module-imports": {
|
"node_modules/@babel/helper-module-imports": {
|
||||||
"version": "7.24.6",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz",
|
||||||
|
@ -255,6 +312,18 @@
|
||||||
"@babel/core": "^7.0.0"
|
"@babel/core": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-optimise-call-expression": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-3SFDJRbx7KuPRl8XDUr8O7GAEB8iGyWPjLKJh/ywP/Iy9WOmEfMrsWbaZpvBu2HSYn4KQygIsz0O7m8y10ncMA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.24.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-plugin-utils": {
|
"node_modules/@babel/helper-plugin-utils": {
|
||||||
"version": "7.24.6",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz",
|
||||||
|
@ -264,6 +333,23 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-replace-supers": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-mRhfPwDqDpba8o1F8ESxsEkJMQkUF8ZIWrAc0FtWhxnjfextxMWxr22RtFizxxSYLjVHDeMgVsRq8BBZR2ikJQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-environment-visitor": "^7.24.6",
|
||||||
|
"@babel/helper-member-expression-to-functions": "^7.24.6",
|
||||||
|
"@babel/helper-optimise-call-expression": "^7.24.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-simple-access": {
|
"node_modules/@babel/helper-simple-access": {
|
||||||
"version": "7.24.6",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz",
|
||||||
|
@ -276,6 +362,18 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/helper-skip-transparent-expression-wrappers": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-jhbbkK3IUKc4T43WadP96a27oYti9gEf1LdyGSP2rHGH77kwLwfhO7TgwnWvxxQVmke0ImmCSS47vcuxEMGD3Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.24.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/helper-split-export-declaration": {
|
"node_modules/@babel/helper-split-export-declaration": {
|
||||||
"version": "7.24.6",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz",
|
||||||
|
@ -593,6 +691,60 @@
|
||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/plugin-transform-modules-commonjs": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-JEV8l3MHdmmdb7S7Cmx6rbNEjRCgTQMZxllveHO0mx6uiclB0NflCawlQQ6+o5ZrwjUBYPzHm2XoK4wqGVUFuw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-module-transforms": "^7.24.6",
|
||||||
|
"@babel/helper-plugin-utils": "^7.24.6",
|
||||||
|
"@babel/helper-simple-access": "^7.24.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/plugin-transform-typescript": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-H0i+hDLmaYYSt6KU9cZE0gb3Cbssa/oxWis7PX4ofQzbvsfix9Lbh8SRk7LCPDlLWJHUiFeHU0qRRpF/4Zv7mQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-annotate-as-pure": "^7.24.6",
|
||||||
|
"@babel/helper-create-class-features-plugin": "^7.24.6",
|
||||||
|
"@babel/helper-plugin-utils": "^7.24.6",
|
||||||
|
"@babel/plugin-syntax-typescript": "^7.24.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/preset-typescript": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-U10aHPDnokCFRXgyT/MaIRTivUu2K/mu0vJlwRS9LxJmJet+PFQNKpggPyFCUtC6zWSBPjvxjnpNkAn3Uw2m5w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-plugin-utils": "^7.24.6",
|
||||||
|
"@babel/helper-validator-option": "^7.24.6",
|
||||||
|
"@babel/plugin-syntax-jsx": "^7.24.6",
|
||||||
|
"@babel/plugin-transform-modules-commonjs": "^7.24.6",
|
||||||
|
"@babel/plugin-transform-typescript": "^7.24.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "^7.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.24.5",
|
"version": "7.24.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
|
||||||
|
@ -1930,14 +2082,12 @@
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.12",
|
"version": "15.7.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
||||||
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
|
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.2",
|
"version": "18.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
|
||||||
"integrity": "sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w==",
|
"integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
|
@ -1947,7 +2097,6 @@
|
||||||
"version": "18.3.0",
|
"version": "18.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
|
||||||
"integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
|
"integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
|
@ -9473,6 +9622,15 @@
|
||||||
"jsesc": "^2.5.1"
|
"jsesc": "^2.5.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/helper-annotate-as-pure": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-DitEzDfOMnd13kZnDqns1ccmftwJTS9DMkyn9pYTxulS7bZxUxpMly3Nf23QQ6NwA4UB8lAqjbqWtyvElEMAkg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.24.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/helper-compilation-targets": {
|
"@babel/helper-compilation-targets": {
|
||||||
"version": "7.24.6",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz",
|
||||||
|
@ -9503,6 +9661,31 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/helper-create-class-features-plugin": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-djsosdPJVZE6Vsw3kk7IPRWethP94WHGOhQTc67SNXE0ZzMhHgALw8iGmYS0TD1bbMM0VDROy43od7/hN6WYcA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-annotate-as-pure": "^7.24.6",
|
||||||
|
"@babel/helper-environment-visitor": "^7.24.6",
|
||||||
|
"@babel/helper-function-name": "^7.24.6",
|
||||||
|
"@babel/helper-member-expression-to-functions": "^7.24.6",
|
||||||
|
"@babel/helper-optimise-call-expression": "^7.24.6",
|
||||||
|
"@babel/helper-replace-supers": "^7.24.6",
|
||||||
|
"@babel/helper-skip-transparent-expression-wrappers": "^7.24.6",
|
||||||
|
"@babel/helper-split-export-declaration": "^7.24.6",
|
||||||
|
"semver": "^6.3.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"semver": {
|
||||||
|
"version": "6.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
|
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/helper-environment-visitor": {
|
"@babel/helper-environment-visitor": {
|
||||||
"version": "7.24.6",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz",
|
||||||
|
@ -9528,6 +9711,15 @@
|
||||||
"@babel/types": "^7.24.6"
|
"@babel/types": "^7.24.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/helper-member-expression-to-functions": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-OTsCufZTxDUsv2/eDXanw/mUZHWOxSbEmC3pP8cgjcy5rgeVPWWMStnv274DV60JtHxTk0adT0QrCzC4M9NWGg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.24.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/helper-module-imports": {
|
"@babel/helper-module-imports": {
|
||||||
"version": "7.24.6",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz",
|
||||||
|
@ -9549,12 +9741,32 @@
|
||||||
"@babel/helper-validator-identifier": "^7.24.6"
|
"@babel/helper-validator-identifier": "^7.24.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/helper-optimise-call-expression": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-3SFDJRbx7KuPRl8XDUr8O7GAEB8iGyWPjLKJh/ywP/Iy9WOmEfMrsWbaZpvBu2HSYn4KQygIsz0O7m8y10ncMA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.24.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/helper-plugin-utils": {
|
"@babel/helper-plugin-utils": {
|
||||||
"version": "7.24.6",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz",
|
||||||
"integrity": "sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg==",
|
"integrity": "sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@babel/helper-replace-supers": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-mRhfPwDqDpba8o1F8ESxsEkJMQkUF8ZIWrAc0FtWhxnjfextxMWxr22RtFizxxSYLjVHDeMgVsRq8BBZR2ikJQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-environment-visitor": "^7.24.6",
|
||||||
|
"@babel/helper-member-expression-to-functions": "^7.24.6",
|
||||||
|
"@babel/helper-optimise-call-expression": "^7.24.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/helper-simple-access": {
|
"@babel/helper-simple-access": {
|
||||||
"version": "7.24.6",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz",
|
||||||
|
@ -9564,6 +9776,15 @@
|
||||||
"@babel/types": "^7.24.6"
|
"@babel/types": "^7.24.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/helper-skip-transparent-expression-wrappers": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-jhbbkK3IUKc4T43WadP96a27oYti9gEf1LdyGSP2rHGH77kwLwfhO7TgwnWvxxQVmke0ImmCSS47vcuxEMGD3Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.24.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/helper-split-export-declaration": {
|
"@babel/helper-split-export-declaration": {
|
||||||
"version": "7.24.6",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz",
|
||||||
|
@ -9793,6 +10014,42 @@
|
||||||
"@babel/helper-plugin-utils": "^7.24.6"
|
"@babel/helper-plugin-utils": "^7.24.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/plugin-transform-modules-commonjs": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-JEV8l3MHdmmdb7S7Cmx6rbNEjRCgTQMZxllveHO0mx6uiclB0NflCawlQQ6+o5ZrwjUBYPzHm2XoK4wqGVUFuw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-module-transforms": "^7.24.6",
|
||||||
|
"@babel/helper-plugin-utils": "^7.24.6",
|
||||||
|
"@babel/helper-simple-access": "^7.24.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/plugin-transform-typescript": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-H0i+hDLmaYYSt6KU9cZE0gb3Cbssa/oxWis7PX4ofQzbvsfix9Lbh8SRk7LCPDlLWJHUiFeHU0qRRpF/4Zv7mQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-annotate-as-pure": "^7.24.6",
|
||||||
|
"@babel/helper-create-class-features-plugin": "^7.24.6",
|
||||||
|
"@babel/helper-plugin-utils": "^7.24.6",
|
||||||
|
"@babel/plugin-syntax-typescript": "^7.24.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/preset-typescript": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-U10aHPDnokCFRXgyT/MaIRTivUu2K/mu0vJlwRS9LxJmJet+PFQNKpggPyFCUtC6zWSBPjvxjnpNkAn3Uw2m5w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-plugin-utils": "^7.24.6",
|
||||||
|
"@babel/helper-validator-option": "^7.24.6",
|
||||||
|
"@babel/plugin-syntax-jsx": "^7.24.6",
|
||||||
|
"@babel/plugin-transform-modules-commonjs": "^7.24.6",
|
||||||
|
"@babel/plugin-transform-typescript": "^7.24.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/runtime": {
|
"@babel/runtime": {
|
||||||
"version": "7.24.5",
|
"version": "7.24.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
|
||||||
|
@ -10815,14 +11072,12 @@
|
||||||
"@types/prop-types": {
|
"@types/prop-types": {
|
||||||
"version": "15.7.12",
|
"version": "15.7.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
||||||
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
|
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"@types/react": {
|
"@types/react": {
|
||||||
"version": "18.3.2",
|
"version": "18.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
|
||||||
"integrity": "sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w==",
|
"integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
|
@ -10832,7 +11087,6 @@
|
||||||
"version": "18.3.0",
|
"version": "18.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
|
||||||
"integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
|
"integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,18 +13,19 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.11.4",
|
"@emotion/react": "^11.11.4",
|
||||||
"@emotion/styled": "^11.11.5",
|
"@emotion/styled": "^11.11.5",
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
"framer-motion": "^11.2.6",
|
"framer-motion": "^11.2.6",
|
||||||
"next": "14.2.3",
|
"next": "^14.2.3",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"ts-node-dev": "^2.0.0"
|
"ts-node-dev": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/preset-typescript": "^7.24.6",
|
||||||
"@testing-library/jest-dom": "^6.4.5",
|
"@testing-library/jest-dom": "^6.4.5",
|
||||||
"@testing-library/react": "^15.0.7",
|
"@testing-library/react": "^15.0.7",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
|
||||||
"@types/react-dom": "^18",
|
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "14.2.3",
|
"eslint-config-next": "14.2.3",
|
||||||
|
|
|
@ -3,6 +3,8 @@ const url = `${process.env.OMDB_URL}?apikey=${process.env.API_KEY}`;
|
||||||
export const queryOMDb = async (queryParamString: string) => {
|
export const queryOMDb = async (queryParamString: string) => {
|
||||||
"use server";
|
"use server";
|
||||||
try {
|
try {
|
||||||
|
// await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
|
||||||
const queryUrl = `${url}&${queryParamString}`;
|
const queryUrl = `${url}&${queryParamString}`;
|
||||||
const response = await fetch(queryUrl);
|
const response = await fetch(queryUrl);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
'use client'
|
"use client";
|
||||||
|
|
||||||
import { FavoriteContext } from "@/components/FavoriteContext";
|
import { FavoriteContext } from "@/common/components/FavoriteContext";
|
||||||
|
import MovieList from "@/common/components/MovieList";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
|
||||||
export default () => {
|
export default function FavoriteList() {
|
||||||
const {favorites} = useContext(FavoriteContext)
|
const { favorites } = useContext(FavoriteContext);
|
||||||
return (
|
return (
|
||||||
<ul>
|
<MovieList movies={favorites} />
|
||||||
{favorites.map((f, i) => (
|
|
||||||
<li key={i}>{f.Title}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import FavoriteList from "./favoriteList";
|
import FavoriteList from "./favoriteList";
|
||||||
|
|
||||||
export default () => {
|
export default function Favorites() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Your Favorites!</h1>
|
<h1>Your Favorites!</h1>
|
||||||
|
|
5
movie-search/src/app/Movie/loading.tsx
Normal file
5
movie-search/src/app/Movie/loading.tsx
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import Loading from "@/common/components/Loading";
|
||||||
|
|
||||||
|
export default function loading() {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
|
@ -1,21 +1,22 @@
|
||||||
import { queryOMDb } from "@/api/omdb";
|
import { queryOMDb } from "@/api/omdb";
|
||||||
import { INextJsProps, stringifyQueryParams } from "../Search/page";
|
import { INextJsProps } from "../Search/page";
|
||||||
import ImgCard from "@/components/ImgCard";
|
import ImgCard from "@/common/components/ImgCard";
|
||||||
|
import { stringifyQueryParams } from "@/common/utils/searchParams";
|
||||||
|
|
||||||
export default async ({ searchParams }: INextJsProps) => {
|
export default async function Movie({ searchParams }: INextJsProps) {
|
||||||
const movie = await queryOMDb(stringifyQueryParams(searchParams!));
|
const movie = await queryOMDb(stringifyQueryParams(searchParams!));
|
||||||
const { Poster, Ratings, ...rest } = movie;
|
const { Poster, Title, Ratings, ...details } = movie;
|
||||||
return (
|
return (
|
||||||
<div className="rounded p-4 bg-gray-300 w-auto mx-4 flex-column">
|
<div className="rounded p-4 bg-gray-300 w-auto mx-4 flex-column h-xl">
|
||||||
<ImgCard
|
<ImgCard
|
||||||
width={400}
|
width={400}
|
||||||
height={580}
|
height={580}
|
||||||
src={movie.Poster !== "N/A" ? movie.Poster : ""}
|
src={Poster !== "N/A" ? Poster : ""}
|
||||||
alt={`${movie.Title} Movie Poster`}
|
alt={`${details.Title} Movie Poster`}
|
||||||
>
|
>
|
||||||
<div className="mt-auto">
|
<div className="mt-auto">
|
||||||
<h3 className="mb-2 text-lg font-semibold">{movie.Title}</h3>
|
<h3 className="mb-2 text-lg font-semibold">{Title}</h3>
|
||||||
{Object.entries(rest).map(([key, value]) => {
|
{Object.entries(details).map(([key, value]) => {
|
||||||
return (
|
return (
|
||||||
<p key={key} className="mb-2 text-sm text-gray-700">
|
<p key={key} className="mb-2 text-sm text-gray-700">
|
||||||
<strong>{key}: </strong>
|
<strong>{key}: </strong>
|
||||||
|
@ -25,7 +26,7 @@ export default async ({ searchParams }: INextJsProps) => {
|
||||||
})}
|
})}
|
||||||
<h3>Ratings:</h3>
|
<h3>Ratings:</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{movie.Ratings.map((r: { Source: string; Value: string }) => (
|
{Ratings.map((r: { Source: string; Value: string }) => (
|
||||||
<li key={r.Source}>
|
<li key={r.Source}>
|
||||||
{r.Source} - {r.Value}
|
{r.Source} - {r.Value}
|
||||||
</li>
|
</li>
|
||||||
|
@ -35,4 +36,4 @@ export default async ({ searchParams }: INextJsProps) => {
|
||||||
</ImgCard>
|
</ImgCard>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
import FavoriteBtn from "@/components/FavoriteBtn";
|
|
||||||
import ImgCard from "@/components/ImgCard";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
export interface IMovieCardItem {
|
|
||||||
Title: string;
|
|
||||||
Year: string;
|
|
||||||
imdbID: string;
|
|
||||||
Type: string;
|
|
||||||
Poster: string;
|
|
||||||
}
|
|
||||||
interface IMovieCardProps {
|
|
||||||
movie: IMovieCardItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ({ movie }: IMovieCardProps) => {
|
|
||||||
return (
|
|
||||||
<ImgCard
|
|
||||||
classNames={`w-48`}
|
|
||||||
width={140}
|
|
||||||
height={160}
|
|
||||||
src={movie.Poster !== "N/A" ? movie.Poster : ""}
|
|
||||||
alt={`${movie.Title} Movie Poster`}
|
|
||||||
>
|
|
||||||
<div className="mt-auto">
|
|
||||||
<h3 className="mb-2 text-lg font-semibold">{movie.Title}</h3>
|
|
||||||
<p className="mb-2 text-sm text-gray-700">Circa: {movie.Year}</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-3">
|
|
||||||
<Link
|
|
||||||
href={`/Movie?i=${movie.imdbID}`}
|
|
||||||
className="block rounded-lg bg-purple-500 px-4 py-2 text-center font-semibold text-white hover:bg-purple-600"
|
|
||||||
>
|
|
||||||
Movie Details
|
|
||||||
</Link>
|
|
||||||
<FavoriteBtn movie={movie} />
|
|
||||||
</div>
|
|
||||||
</ImgCard>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,19 +0,0 @@
|
||||||
import MovieCard, { IMovieCardItem } from "./MovieCard";
|
|
||||||
|
|
||||||
interface IMovieListProps {
|
|
||||||
movies: Array<IMovieCardItem>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ({ movies }: IMovieListProps) => {
|
|
||||||
return (
|
|
||||||
<div className="container m-auto">
|
|
||||||
<div className={`p-4 flex justify-start flex-wrap gap-4`}>
|
|
||||||
{movies.map((m) => (
|
|
||||||
<div className="WrapItem" key={m.imdbID}>
|
|
||||||
<MovieCard movie={m} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,20 +1,24 @@
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export default () => {
|
export default function SearchBar() {
|
||||||
const searchMovies = async (formData: FormData) => {
|
const searchMovies = async (formData: FormData) => {
|
||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
const movieTitle = formData.get("movieTitle");
|
const movieTitle = formData.get("movieTitle");
|
||||||
|
// revalidatePath('/Search')
|
||||||
redirect(`/Search?s=${movieTitle}`);
|
redirect(`/Search?s=${movieTitle}`);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<form action={searchMovies} className={`flex gap-4 m-4 justify-center`}>
|
<form action={searchMovies} className={`md:flex gap-4 m-4 justify-center`}>
|
||||||
|
<div className="flex justify-center">
|
||||||
<input
|
<input
|
||||||
className="rounded px-2"
|
className="rounded px-2 mb-2 md:mb-0"
|
||||||
placeholder="Search by Title"
|
placeholder="Search by Title"
|
||||||
name="movieTitle"
|
name="movieTitle"
|
||||||
/>
|
/>
|
||||||
<div className="flex gap-4">
|
</div>
|
||||||
|
<div className="flex gap-4 justify-center">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className={`rounded px-2 bg-purple-500 hover:bg-purple-700`}
|
className={`rounded px-2 bg-purple-500 hover:bg-purple-700`}
|
||||||
|
@ -30,4 +34,4 @@ export default () => {
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
16
movie-search/src/app/Search/SearchPage.tsx
Normal file
16
movie-search/src/app/Search/SearchPage.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { queryOMDb } from "@/api/omdb";
|
||||||
|
import MovieList from "../../common/components/MovieList";
|
||||||
|
|
||||||
|
export default async function SearchPage({
|
||||||
|
queryParamString,
|
||||||
|
}: {
|
||||||
|
queryParamString: string;
|
||||||
|
}) {
|
||||||
|
|
||||||
|
const { Search } = await queryOMDb(queryParamString);
|
||||||
|
const movies = Search || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MovieList movies={movies} />
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,29 +1,22 @@
|
||||||
import { queryOMDb } from "@/api/omdb";
|
import { stringifyQueryParams } from "@/common/utils/searchParams";
|
||||||
import MovieList from "./MovieList";
|
import { Suspense } from "react";
|
||||||
|
import SearchPage from "./SearchPage";
|
||||||
import SearchBar from "./SearchBar";
|
import SearchBar from "./SearchBar";
|
||||||
|
import Loading from "@/common/components/Loading";
|
||||||
|
|
||||||
export interface INextJsProps {
|
export interface INextJsProps {
|
||||||
searchParams?: object;
|
searchParams?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const stringifyQueryParams = (searchParams: object) =>
|
export default async function Search({ searchParams }: INextJsProps) {
|
||||||
Object.entries(searchParams)
|
|
||||||
.map(
|
|
||||||
([key, value], i, arr) =>
|
|
||||||
`${key}=${value}${i < arr.length - 1 ? "&" : ""}`
|
|
||||||
)
|
|
||||||
.toString();
|
|
||||||
|
|
||||||
export default async ({ searchParams }: INextJsProps) => {
|
|
||||||
const queryParamString = stringifyQueryParams(searchParams!);
|
const queryParamString = stringifyQueryParams(searchParams!);
|
||||||
// console.log(queryParamString)
|
|
||||||
const { Search } = await queryOMDb(queryParamString);
|
|
||||||
const movies = Search || [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`mx-auto px-2`}>
|
<div className={`mx-auto px-2`}>
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
<MovieList movies={movies} />
|
<Suspense key={queryParamString} fallback={<Loading />}>
|
||||||
|
<SearchPage queryParamString={queryParamString} />
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -51,12 +51,12 @@
|
||||||
--background-start-rgb: 0, 0, 0;
|
--background-start-rgb: 0, 0, 0;
|
||||||
--background-end-rgb: 0, 0, 0;
|
--background-end-rgb: 0, 0, 0;
|
||||||
|
|
||||||
--primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
|
--primary-glow: radial-gradient(rgba(210, 1, 255, 0.5), rgba(1, 65, 255, 0));
|
||||||
--secondary-glow: linear-gradient(
|
--secondary-glow: linear-gradient(
|
||||||
to bottom right,
|
to bottom right,
|
||||||
rgba(1, 65, 255, 0),
|
rgba(1, 65, 255, 0),
|
||||||
rgba(1, 65, 255, 0),
|
rgba(1, 65, 255, 0),
|
||||||
rgba(1, 65, 255, 0.3)
|
rgba(180, 1, 255, 0.3)
|
||||||
);
|
);
|
||||||
|
|
||||||
--tile-start-rgb: 2, 13, 46;
|
--tile-start-rgb: 2, 13, 46;
|
||||||
|
@ -92,6 +92,7 @@ body {
|
||||||
|
|
||||||
body {
|
body {
|
||||||
color: rgb(var(--foreground-rgb));
|
color: rgb(var(--foreground-rgb));
|
||||||
|
/* background: #2bff0110 */
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
to bottom,
|
to bottom,
|
||||||
transparent,
|
transparent,
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import NavBar from "@/components/navBar";
|
import NavBar from "@/common/components/NavBar";
|
||||||
import { FavoriteProvider } from "@/components/FavoriteContext";
|
import { FavoriteProvider } from "@/common/components/FavoriteContext";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "Movie Search App",
|
||||||
description: "Generated by create next app",
|
description: "Powered by Nextjs and OMDb.",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
@ -17,8 +17,8 @@ export default function RootLayout({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en" className="h-full">
|
||||||
<body className={`${inter.className} mb-4`}>
|
<body className={`${inter.className} mb-4 bg-black h-full`}>
|
||||||
<NavBar />
|
<NavBar />
|
||||||
<FavoriteProvider>
|
<FavoriteProvider>
|
||||||
{children}
|
{children}
|
||||||
|
|
1
movie-search/src/common/classes.ts
Normal file
1
movie-search/src/common/classes.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
const responsiveContainer = ''
|
29
movie-search/src/common/components/FavoriteBtn.tsx
Normal file
29
movie-search/src/common/components/FavoriteBtn.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useContext } from "react";
|
||||||
|
import { FavoriteContext } from "./FavoriteContext";
|
||||||
|
|
||||||
|
export default function FavoriteBtn({ movie }: any) {
|
||||||
|
const { favorites, setFavorites } = useContext(FavoriteContext);
|
||||||
|
const filteredFavorites = favorites.filter((f) => f.imdbID != movie.imdbID);
|
||||||
|
const isFavorite =
|
||||||
|
favorites.filter((f) => f.imdbID == movie.imdbID).length == 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (isFavorite) {
|
||||||
|
setFavorites([...filteredFavorites]);
|
||||||
|
} else {
|
||||||
|
setFavorites([...favorites, movie]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`block text-yellow-500 rounded my-auto h-8 w-8
|
||||||
|
${
|
||||||
|
isFavorite ? " bg-black" : "border-2 border-gray-300 bg-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
★
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
'use client'
|
'use client'
|
||||||
import { IMovieCardItem } from "@/app/Search/MovieCard";
|
|
||||||
import { PropsWithChildren, createContext, useState } from "react";
|
import { PropsWithChildren, createContext, useState } from "react";
|
||||||
|
import { IMovieCardItem } from "./MovieCard";
|
||||||
|
|
||||||
interface IFavoriteContext {
|
interface IFavoriteContext {
|
||||||
favorites: Array<IMovieCardItem>
|
favorites: Array<IMovieCardItem>
|
|
@ -7,19 +7,18 @@ interface ICardProps extends PropsWithChildren {
|
||||||
alt: string;
|
alt: string;
|
||||||
classNames?: string;
|
classNames?: string;
|
||||||
}
|
}
|
||||||
export default ({
|
export default function ImgCard({
|
||||||
classNames,
|
classNames,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
src,
|
src,
|
||||||
alt,
|
alt,
|
||||||
children,
|
children,
|
||||||
}: ICardProps) => {
|
}: ICardProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
shadow-md
|
shadow-lg
|
||||||
shadow
|
|
||||||
shadow-purple-500
|
shadow-purple-500
|
||||||
rounded
|
rounded
|
||||||
|
|
9
movie-search/src/common/components/Loading.tsx
Normal file
9
movie-search/src/common/components/Loading.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export default function Loading() {
|
||||||
|
return (
|
||||||
|
<div className="h-svh flex flex-col justify-center">
|
||||||
|
<h1 className="my-auto text-center">
|
||||||
|
<strong>Loading...</strong>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
41
movie-search/src/common/components/MovieCard.tsx
Normal file
41
movie-search/src/common/components/MovieCard.tsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import FavoriteBtn from "@/common/components/FavoriteBtn";
|
||||||
|
import ImgCard from "@/common/components/ImgCard";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export interface IMovieCardItem {
|
||||||
|
Title: string;
|
||||||
|
Year: string;
|
||||||
|
imdbID: string;
|
||||||
|
Type: string;
|
||||||
|
Poster: string;
|
||||||
|
}
|
||||||
|
interface IMovieCardProps {
|
||||||
|
movie: IMovieCardItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MovieCard({ movie }: IMovieCardProps) {
|
||||||
|
return (
|
||||||
|
<ImgCard
|
||||||
|
classNames={`w-full h-full flex flex-col justify-between`}
|
||||||
|
width={140}
|
||||||
|
height={160}
|
||||||
|
src={movie.Poster !== "N/A" ? movie.Poster : ""}
|
||||||
|
alt={`${movie.Title} Movie Poster`}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<h3 className=" text-lg font-semibold">{movie.Title}</h3>
|
||||||
|
<p className=" text-sm text-gray-700">Circa: {movie.Year}</p>
|
||||||
|
|
||||||
|
<div className="flex gap-3 justify-around">
|
||||||
|
<Link
|
||||||
|
href={`/Movie?i=${movie.imdbID}`}
|
||||||
|
className="block rounded-lg bg-purple-500 px-4 py-2 text-center font-semibold text-white hover:bg-purple-600"
|
||||||
|
>
|
||||||
|
Movie Details
|
||||||
|
</Link>
|
||||||
|
<FavoriteBtn movie={movie} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ImgCard>
|
||||||
|
);
|
||||||
|
}
|
25
movie-search/src/common/components/MovieList.tsx
Normal file
25
movie-search/src/common/components/MovieList.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import MovieCard, { IMovieCardItem } from "./MovieCard";
|
||||||
|
|
||||||
|
export interface IMovieListProps {
|
||||||
|
movies: Array<IMovieCardItem>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MovieList({ movies }: IMovieListProps) {
|
||||||
|
return (
|
||||||
|
<div className="container m-auto">
|
||||||
|
<div
|
||||||
|
className={`p-1 flex justify-around flex-wrap gap-4 sm:justify-center `}
|
||||||
|
>
|
||||||
|
{movies.length > 0 ? (
|
||||||
|
movies.map((m) => (
|
||||||
|
<div className="WrapItem w-full mx-5 sm:w-64 mx-1" key={m.imdbID}>
|
||||||
|
<MovieCard movie={m} />
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<h1>Nothing to show</h1>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export default () => {
|
export default function NavBar() {
|
||||||
return (
|
return (
|
||||||
<nav className="px-2 flex gap-4">
|
<nav className="px-2 flex gap-4 shadow-md shadow-purple-500 mt-2 mb-4">
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<strong>Home</strong>
|
<strong>Home</strong>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -10,4 +10,4 @@ export default () => {
|
||||||
<Link href="/Favorites">Favorites</Link>
|
<Link href="/Favorites">Favorites</Link>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
};
|
}
|
7
movie-search/src/common/utils/searchParams.ts
Normal file
7
movie-search/src/common/utils/searchParams.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export const stringifyQueryParams = (searchParams: object) =>
|
||||||
|
Object.entries(searchParams)
|
||||||
|
.map(
|
||||||
|
([key, value], i, arr) =>
|
||||||
|
`${key}=${value}${i < arr.length - 1 ? "&" : ""}`
|
||||||
|
)
|
||||||
|
.toString();
|
|
@ -1,25 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { useContext } from "react";
|
|
||||||
import { FavoriteContext } from "./FavoriteContext";
|
|
||||||
|
|
||||||
export default ({ movie }: any) => {
|
|
||||||
const { favorites, setFavorites } = useContext(FavoriteContext);
|
|
||||||
const filteredFavorites = favorites.filter(f => f.imdbID != movie.imdbID)
|
|
||||||
const isFavorite = favorites.filter(f => f.imdbID == movie.imdbID).length == 1
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
if (isFavorite) {
|
|
||||||
setFavorites([...filteredFavorites])
|
|
||||||
} else {
|
|
||||||
setFavorites([...favorites, movie]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={`block text-yellow-500 rounded my-auto w-6 ${isFavorite ? 'bg-black' : 'bg-white'}`}
|
|
||||||
>
|
|
||||||
★
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user