diff --git a/package.json b/package.json index 5aa1086..343869f 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,11 @@ "@tanstack/react-query-devtools": "^5.0.0-alpha.91", "axios": "^1.7.2", "cors": "^2.8.5", + "formik": "^2.4.6", "next": "14.2.4", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "yup": "^1.4.0" }, "devDependencies": { "eslint": "^8", diff --git a/src/app/containers/create-movie/formField.jsx b/src/app/containers/create-movie/formField.jsx new file mode 100644 index 0000000..a825e30 --- /dev/null +++ b/src/app/containers/create-movie/formField.jsx @@ -0,0 +1,18 @@ +import { Field } from "formik"; + +export const FormField = ({ label, placeholder, name, type, error }) => ( +
+ + + {error &&
{error}
} +
+); \ No newline at end of file diff --git a/src/app/containers/create-movie/index.jsx b/src/app/containers/create-movie/index.jsx index 8c5adf8..d152b52 100644 --- a/src/app/containers/create-movie/index.jsx +++ b/src/app/containers/create-movie/index.jsx @@ -1,28 +1,33 @@ -'use client' +import { useMutation } from "@tanstack/react-query"; +import axios from "axios"; +import MovieForm from "./movieForm"; -import { useMutation } from "@tanstack/react-query" -import axios from "axios" - -const createMovie = async () => { - const response = await axios.post('/api/movies', {id: 7, name: "Dune" }) - - return response.data -} +const createMovie = async (movieData) => { + const response = await axios.post("/api/movies", movieData); + return response.data; +}; export const CreateMovie = () => { - const { mutate, data, isSuccess, isError, error } = useMutation({ - mutationFn: createMovie, - mutationKey: ["create-movie"] - }) + const { mutate : handleMovieSubmit, data, isSuccess, isError, error } = useMutation({ + mutationFn: createMovie, + mutationKey: ["create-movie"], + }); - return <> - {isError &&
{error.message}
} - - {isSuccess &&
- Newly created movie -

This is id: {data.id}

-

This is movie name: {data.name}

-
} - -} \ No newline at end of file + return ( +
+ + {isError && ( +
+ {error.response?.data?.message || "An error occurred"} +
+ )} + {isSuccess && ( +
+ Newly created movie +

Movie name: {data.title}

+
+ )} +
+ ); +}; diff --git a/src/app/containers/create-movie/movieForm.jsx b/src/app/containers/create-movie/movieForm.jsx new file mode 100644 index 0000000..4e7f948 --- /dev/null +++ b/src/app/containers/create-movie/movieForm.jsx @@ -0,0 +1,104 @@ +"use client"; +import React from "react"; +import { Formik, Form } from "formik"; +import { FormField } from "./FormField"; +import * as yup from "yup"; + +const initialValues = { + title: "", + description: "", + genres: { + id: "", + name: "", + }, + cast: { + role: "", + crew: { + id: "", + nae: "", + }, + }, +}; + +const validationSchema = yup.object().shape({ + title: yup.string().required("Title is required"), + description: yup.string().required("Description is required"), + genres: yup.object().shape({ + id: yup.number().required("Genre ID is required"), + name: yup.string().required("Genre name is required"), + }), + cast: yup.object().shape({ + role: yup.string().required("Cast role is required"), + crew: yup.object().shape({ + id: yup.number().required("Crew ID is required"), + name: yup.string().required("Crew name is required"), + }), + }), +}); + +const MovieForm = ({ handleSubmit }) => { + return ( + { + handleSubmit(values); + }} + validationSchema={validationSchema} + > + {({ errors }) => ( +
+ + + + + + + + + + )} +
+ ); +}; + +export default MovieForm; diff --git a/src/app/containers/movies/content.jsx b/src/app/containers/movies/content.jsx index 515fffe..9228458 100644 --- a/src/app/containers/movies/content.jsx +++ b/src/app/containers/movies/content.jsx @@ -10,7 +10,7 @@ export const Content = () => {

Movies List

{isError && ( diff --git a/src/app/create-movie/page.jsx b/src/app/create-movie/page.jsx index 5c47e83..7beb076 100644 --- a/src/app/create-movie/page.jsx +++ b/src/app/create-movie/page.jsx @@ -1,10 +1,16 @@ +'use client' import { CreateMovie } from "../containers/create-movie" + const CreateMoviePage = () => { - return
- Create Movie - + + return ( +
+

Create Movie

+
-} + ); + +}; export default CreateMoviePage \ No newline at end of file diff --git a/src/app/layout.js b/src/app/layout.js index a1c1f04..5673e58 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -14,7 +14,7 @@ export default function RootLayout({ children }) { return (
diff --git a/src/pages/api/movies.js b/src/pages/api/movies.js index d171242..1e5f60b 100644 --- a/src/pages/api/movies.js +++ b/src/pages/api/movies.js @@ -16,11 +16,13 @@ const handler = (req, res) => { const data = JSON.parse(fs.readFileSync(dataFilePath, "utf8")); const alreadyExists = data.some( - (movie) => movie.id === newData.id || movie.name === newData.name, + (movie) => + movie.title === newData.title && + movie.genres.id === newData.genres.id ); if (alreadyExists) { - res.status(419).end("The movie already exists"); + res.status(419).json({ message: "The movie already exists" }); return; } data.push(newData); @@ -29,7 +31,7 @@ const handler = (req, res) => { res.status(200).json(newData); } catch { - res.status(500).end("Something went wrong"); + res.status(500).end("Something went wrong" ); } return; diff --git a/src/pages/api/movies.json b/src/pages/api/movies.json index 2e46255..37ee5fa 100644 --- a/src/pages/api/movies.json +++ b/src/pages/api/movies.json @@ -1,26 +1,152 @@ [ { - "id": 1, - "name": "Interstellar" + "title": "Inception", + "description": "A mind-bending thriller where dreams are the ultimate weapon.", + "genres": { + "id": 1, + "name": "Science Fiction" + }, + "cast": { + "role": "Lead", + "crew": { + "id": 1, + "name": "Christopher Nolan" + } + } }, { - "id": 2, - "name": "Avatar" + "title": "The Dark Knight", + "description": "Batman faces off against the Joker in a battle for Gotham City.", + "genres": { + "id": 2, + "name": "Action" + }, + "cast": { + "role": "Supporting", + "crew": { + "id": 2, + "name": "Christopher Nolan" + } + } }, { - "id": 3, - "name": "Lord of the Rings" + "title": "Pulp Fiction", + "description": "The lives of two mob hitmen, a boxer, and a pair of diner bandits intertwine.", + "genres": { + "id": 3, + "name": "Crime" + }, + "cast": { + "role": "Lead", + "crew": { + "id": 3, + "name": "Quentin Tarantino" + } + } }, { - "id": 4, - "name": "Titanic" + "title": "The Shawshank Redemption", + "description": "Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.", + "genres": { + "id": 4, + "name": "Drama" + }, + "cast": { + "role": "Lead", + "crew": { + "id": 4, + "name": "Frank Darabont" + } + } }, { - "id": 5, - "name": "Hobbit" + "title": "Forrest Gump", + "description": "The presidencies of Kennedy and Johnson, the Vietnam War, the Watergate scandal and other historical events unfold from the perspective of an Alabama man with an IQ of 75.", + "genres": { + "id": 5, + "name": "Drama" + }, + "cast": { + "role": "Lead", + "crew": { + "id": 5, + "name": "Robert Zemeckis" + } + } }, { - "id": 7, - "name": "Dune" + "title": "The Matrix", + "description": "A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers.", + "genres": { + "id": 6, + "name": "Science Fiction" + }, + "cast": { + "role": "Lead", + "crew": { + "id": 6, + "name": "The Wachowskis" + } + } + }, + { + "title": "Fight Club", + "description": "An insomniac office worker and a devil-may-care soap maker form an underground fight club that evolves into something much, much more.", + "genres": { + "id": 7, + "name": "Drama" + }, + "cast": { + "role": "Lead", + "crew": { + "id": 7, + "name": "David Fincher" + } + } + }, + { + "title": "The Godfather", + "description": "The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.", + "genres": { + "id": 8, + "name": "Crime" + }, + "cast": { + "role": "Lead", + "crew": { + "id": 8, + "name": "Francis Ford Coppola" + } + } + }, + { + "title": "The Lord of the Rings: The Fellowship of the Ring", + "description": "A meek Hobbit from the Shire and eight companions set out on a journey to destroy the powerful One Ring and save Middle-earth from the Dark Lord Sauron.", + "genres": { + "id": 9, + "name": "Fantasy" + }, + "cast": { + "role": "Lead", + "crew": { + "id": 9, + "name": "Peter Jackson" + } + } + }, + { + "title": "Star Wars: Episode IV - A New Hope", + "description": "Luke Skywalker joins forces with a Jedi Knight, a cocky pilot, a Wookiee, and two droids to save the galaxy from the Empire's world-destroying battle station, while also attempting to rescue Princess Leia from the mysterious Darth Vader.", + "genres": { + "id": 10, + "name": "Science Fiction" + }, + "cast": { + "role": "Lead", + "crew": { + "id": 10, + "name": "George Lucas" + } + } } -] \ No newline at end of file +] diff --git a/yarn.lock b/yarn.lock index 7e2e6b6..446f20f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -232,11 +232,32 @@ dependencies: "@tanstack/query-core" "5.50.1" +"@types/hoist-non-react-statics@^3.3.1": + version "3.3.5" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494" + integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/prop-types@*": + version "15.7.12" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" + integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== + +"@types/react@*": + version "18.3.3" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f" + integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0": version "7.2.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz" @@ -646,6 +667,11 @@ cssesc@^3.0.0: resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" @@ -721,6 +747,11 @@ deep-is@^0.1.3: resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +deepmerge@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== + define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz" @@ -1258,6 +1289,20 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +formik@^2.4.6: + version "2.4.6" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.6.tgz#4da75ca80f1a827ab35b08fd98d5a76e928c9686" + integrity sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g== + dependencies: + "@types/hoist-non-react-statics" "^3.3.1" + deepmerge "^2.1.1" + hoist-non-react-statics "^3.3.0" + lodash "^4.17.21" + lodash-es "^4.17.21" + react-fast-compare "^2.0.1" + tiny-warning "^1.0.2" + tslib "^2.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" @@ -1449,6 +1494,13 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + ignore@^5.2.0: version "5.3.1" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz" @@ -1816,11 +1868,21 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" @@ -2209,7 +2271,7 @@ prettier-linter-helpers@^1.0.0: prettier@^3.3.2: version "3.3.2" - resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a" integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA== prop-types@^15.8.1: @@ -2221,6 +2283,11 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +property-expr@^2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" + integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== + proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" @@ -2244,7 +2311,12 @@ react-dom@^18: loose-envify "^1.1.0" scheduler "^0.23.2" -react-is@^16.13.1: +react-fast-compare@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -2651,6 +2723,16 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" +tiny-case@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" + integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q== + +tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" @@ -2658,6 +2740,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== + ts-api-utils@^1.0.1: version "1.3.0" resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz" @@ -2678,7 +2765,7 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.4.0, tslib@^2.6.2: +tslib@^2.0.0, tslib@^2.4.0, tslib@^2.6.2: version "2.6.3" resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== @@ -2695,6 +2782,11 @@ type-fest@^0.20.2: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + typed-array-buffer@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz" @@ -2860,3 +2952,13 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yup@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/yup/-/yup-1.4.0.tgz#898dcd660f9fb97c41f181839d3d65c3ee15a43e" + integrity sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg== + dependencies: + property-expr "^2.0.5" + tiny-case "^1.0.3" + toposort "^2.0.2" + type-fest "^2.19.0"