diff --git a/materials/simple_api/package.json b/materials/simple_api/package.json index a2cfc54..0395ea5 100644 --- a/materials/simple_api/package.json +++ b/materials/simple_api/package.json @@ -7,6 +7,7 @@ }, "dependencies": { "body-parser": "^1.19.0", + "cors": "^2.8.5", "express": "^4.17.1", "nanoid": "^3.3.6" } diff --git a/materials/simple_api/server.js b/materials/simple_api/server.js index d7688d5..0996787 100644 --- a/materials/simple_api/server.js +++ b/materials/simple_api/server.js @@ -2,10 +2,12 @@ const express = require("express"); const api = require("./api"); const bodyParser = require("body-parser"); const port = 3001; +const cors = require("cors"); const app = express(); +app.use(cors({ origin: true, credentials: true })); -app.use(function (req, res, next) { +app.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Methods", "*"); res.header( @@ -17,7 +19,8 @@ app.use(function (req, res, next) { app.use(bodyParser.json()); app.use("/api", api); -app.listen(port, "localhost", function (err) { + +app.listen(port, "localhost", function(err) { if (err) { console.log(err); return; diff --git a/materials/simple_api/yarn.lock b/materials/simple_api/yarn.lock index 6e8a00f..75e3d14 100644 --- a/materials/simple_api/yarn.lock +++ b/materials/simple_api/yarn.lock @@ -58,6 +58,14 @@ cookie@0.4.1: resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz" integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + debug@2.6.9: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" @@ -234,6 +242,11 @@ negotiator@0.6.2: resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +object-assign@^4: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" @@ -351,7 +364,7 @@ utils-merge@1.0.1: resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -vary@~1.1.2: +vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== diff --git a/src/components/restaurant-comment-item/hooks.js b/src/components/restaurant-comment-item/hooks.js new file mode 100644 index 0000000..635273d --- /dev/null +++ b/src/components/restaurant-comment-item/hooks.js @@ -0,0 +1,28 @@ +import { useState } from "react"; +import { useUpdateReviewMutation } from "../../data/services/api/api.js"; + +export const useCommentEditor = () => { + const [editMode, setEditMode] = useState(false); + const [correctedText, setCorrectedText] = useState(""); + const [correctedRating, setCorrectedRating] = useState(""); + + const [updateReview] = useUpdateReviewMutation(); + + const editCommentHandler = ({ reviewId, text, userId, rating }) => { + setEditMode(!editMode); + if (editMode) { + setCorrectedText(text); + setCorrectedRating(rating); + updateReview({ + reviewId, + review: { + text, + userId, + rating + } + }); + } + }; + + return { editMode, editCommentHandler, correctedText, correctedRating }; +}; \ No newline at end of file diff --git a/src/components/restaurant-comment-item/restaurant-comment-item-container.jsx b/src/components/restaurant-comment-item/restaurant-comment-item-container.jsx index fb712a3..1a4fdd6 100644 --- a/src/components/restaurant-comment-item/restaurant-comment-item-container.jsx +++ b/src/components/restaurant-comment-item/restaurant-comment-item-container.jsx @@ -1,16 +1,28 @@ import RestaurantCommentItem from "./restaurant-comment-item.jsx"; -import { useSelector } from "react-redux"; -import { selectReviewById } from "../../data/entities/reviews/slice.js"; -import { selectUserById } from "../../data/entities/users/slice.js"; +import { useGetUsersQuery } from "../../data/services/api/api.js"; +import { FULFILLED } from "../../data/entities/request/sliсe.js"; +import { useCommentEditor } from "./hooks.js"; -export const RestaurantCommentItemContainer = ({ commentId }) => { - const comment = useSelector((state) => selectReviewById(state, commentId)); - const user = useSelector((state) => selectUserById(state, comment.userId)); - return ( - { + // const comment = useSelector((state) => selectReviewById(state, commentId)); + // const user = useSelector((state) => selectUserById(state, userId)); + + const { correctedComment, correctedRating,editMode, editCommentHandler } = useCommentEditor(); + + const { data: users, status } = useGetUsersQuery(); + + const user = users?.find((u) => u.id === userId); + + const showEditButton = currentUserId === userId; + + return status === FULFILLED ? ( + - ); + ) : null; }; diff --git a/src/components/restaurant-comment-item/restaurant-comment-item.jsx b/src/components/restaurant-comment-item/restaurant-comment-item.jsx index de06d3a..69fb8f4 100644 --- a/src/components/restaurant-comment-item/restaurant-comment-item.jsx +++ b/src/components/restaurant-comment-item/restaurant-comment-item.jsx @@ -1,11 +1,41 @@ import styles from "./restaurant-comment-item.module.css"; import { StarsRating } from "../stars-rating/stars-rating.jsx"; +import { Button } from "../button/button.jsx"; +import { useRef } from "react"; + +function RestaurantCommentItem({ + user, + text, + rating, + showEditButton, + editMode, + reviewId, + editCommentHandler + }) { + + const refText = useRef(null); + const refRating = useRef(null); -function RestaurantCommentItem({ user, text, rating }) { return (
  • - {user}: {text} + {user.name}: + {editMode ? ( + <> + + + + ) : ( + {text} + )} + {showEditButton ? ( +
  • diff --git a/src/components/restaurant-comments/restaurant-comments.jsx b/src/components/restaurant-comments/restaurant-comments.jsx index 37ecadc..a71e016 100644 --- a/src/components/restaurant-comments/restaurant-comments.jsx +++ b/src/components/restaurant-comments/restaurant-comments.jsx @@ -1,14 +1,21 @@ import { RestaurantCommentItemContainer } from "../restaurant-comment-item/restaurant-comment-item-container.jsx"; -export default function RestaurantComments({ commentsIds }) { - if (!commentsIds) { +export default function RestaurantComments({ reviews, userId }) { + if (!reviews) { return null; } return ( ); diff --git a/src/components/restaurant-tab-container/restaurant-tab-container.jsx b/src/components/restaurant-tab-container/restaurant-tab-container.jsx index 47d69cc..d6e0bca 100644 --- a/src/components/restaurant-tab-container/restaurant-tab-container.jsx +++ b/src/components/restaurant-tab-container/restaurant-tab-container.jsx @@ -1,21 +1,16 @@ -import { selectRestaurantById } from "../../data/entities/restaurants/slice.js"; -import { useSelector } from "react-redux"; import { Link } from "../link/link.jsx"; import styles from "../tab/tabs.module.css"; import { MENU_PAGE, RESTAURANT_PAGE } from "../../pages/links-paths.js"; -export const RestaurantTabContainer = ({ restaurantId }) => { - const restaurant = useSelector((state) => - selectRestaurantById(state, restaurantId), - ); +export const RestaurantTabContainer = ({ restaurantId, restaurantName }) => { return ( - {restaurant.name} + {restaurantName} ); }; diff --git a/src/components/review-form/review-form.jsx b/src/components/review-form/review-form.jsx index 7df0fe3..3b3a946 100644 --- a/src/components/review-form/review-form.jsx +++ b/src/components/review-form/review-form.jsx @@ -8,7 +8,7 @@ const isFormClear = (form) => { return !Object.values(form).find((value) => !!value); }; -export const ReviewForm = memo(({ restaurantId }) => { +export const ReviewForm = memo(({ restaurantId, onSubmit }) => { const { form, setName, @@ -16,7 +16,6 @@ export const ReviewForm = memo(({ restaurantId }) => { incrementRating, decrementRating, clear, - submit, } = useForm({ restaurantId }); const { name, comment, rating } = form; @@ -61,7 +60,7 @@ export const ReviewForm = memo(({ restaurantId }) => { diff --git a/src/data/redux/store.js b/src/data/redux/store.js index 963c368..c8a1ed6 100644 --- a/src/data/redux/store.js +++ b/src/data/redux/store.js @@ -5,6 +5,7 @@ import { reviewsSlice } from "../entities/reviews/slice.js"; import { usersSlice } from "../entities/users/slice.js"; import { cartSlice } from "../entities/cart/slice.js"; import { requestSlice } from "../entities/request/sliсe.js"; +import { api } from "../services/api/api.js"; const loggerMiddleware = (store) => (next) => (action) => { console.log(action); @@ -18,8 +19,9 @@ export const store = configureStore({ [reviewsSlice.name]: reviewsSlice.reducer, [usersSlice.name]: usersSlice.reducer, [cartSlice.name]: cartSlice.reducer, - [requestSlice.name]: requestSlice.reducer + [requestSlice.name]: requestSlice.reducer, + [api.reducerPath]: api.reducer }, middleware: (getDefaultMiddlewares) => - getDefaultMiddlewares().concat(loggerMiddleware) + getDefaultMiddlewares().concat( api.middleware) }); \ No newline at end of file diff --git a/src/data/services/api/api.js b/src/data/services/api/api.js new file mode 100644 index 0000000..a4e2634 --- /dev/null +++ b/src/data/services/api/api.js @@ -0,0 +1,65 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +export const api = createApi({ + reducerPath: "api", + // pollingInterval: 5000, + baseQuery: fetchBaseQuery({ + baseUrl: "http://localhost:3001/api" + }), + tagTypes: ["review"], + endpoints: (builder) => ({ + getRestaurants: builder.query({ + query: () => "/restaurants", + keepUnusedDataFor: 20 + }), + getRestaurantById: builder.query({ + query: (id) => `/restaurant/${id}`, + keepUnusedDataFor: 20 + }), + getDishes: builder.query({ + query: (restaurantId) => `/dishes?restaurantId=${restaurantId}`, + keepUnusedDataFor: 20 + }), + getDishById: builder.query({ + query: (id) => `/dish/${id}`, + keepUnusedDataFor: 20 + }), + getReviews: builder.query({ + query: (restaurantId) => `/reviews?restaurantId=${restaurantId}`, + providesTags: (_, __, id) => [{ type: "review", id }], + keepUnusedDataFor: 20 + }), + addReview: builder.mutation({ + query: ({ restaurantId, review }) => ({ + url: `/review/${restaurantId}`, + method: "POST", + body: review + }), + invalidatesTags: (_, __, { id }) => [{ type: "review", id }] + }), + updateReview: builder.mutation({ + query: ({ reviewId, review }) => ({ + url: `/review/${reviewId}`, + method: "PATCH", + body: review + }), + invalidatesTags: (_, __, { id }) => [{ type: "review", id }] + }), + getUsers: builder.query({ + query: () => `/users`, + keepUnusedDataFor: 20 + }) + }) +}); + +export const { + useGetRestaurantsQuery, + useLazyGetRestaurantsQuery, + useGetRestaurantByIdQuery, + useGetDishesQuery, + useGetDishByIdQuery, + useGetReviewsQuery, + useGetUsersQuery, + useAddReviewMutation, + useUpdateReviewMutation, +} = api; \ No newline at end of file diff --git a/src/layouts/restaurant-page-layout-container.jsx b/src/layouts/restaurant-page-layout-container.jsx index b3b7938..7c10fbc 100644 --- a/src/layouts/restaurant-page-layout-container.jsx +++ b/src/layouts/restaurant-page-layout-container.jsx @@ -1,23 +1,9 @@ -import { useSelector } from "react-redux"; -import { selectRestaurantById, selectRestaurantsIds } from "../data/entities/restaurants/slice.js"; import { RestaurantPageLayout } from "./restaurant-page-layout.jsx"; -import { useParams } from "react-router"; -import { getRestaurants } from "../data/entities/restaurants/get-restaurants.js"; -import { useRequest } from "../data/hooks/use-request.js"; -import { getRestaurant } from "../data/entities/restaurants/get-restaurant.js"; +import { useGetRestaurantsQuery } from "../data/services/api/api.js"; export const RestaurantPageLayoutContainer = () => { - const requestStatus = useRequest(getRestaurants); - const { restaurantId } = useParams(); - const restaurantRequestStatus = useRequest(getRestaurant, restaurantId); - const restaurant = useSelector((state) => - selectRestaurantById(state, restaurantId) - ); + const { data: restaurants, status: requestStatus } = useGetRestaurantsQuery(); - const restaurantsIds = useSelector(selectRestaurantsIds); - - return ; + return ; }; \ No newline at end of file diff --git a/src/layouts/restaurant-page-layout.jsx b/src/layouts/restaurant-page-layout.jsx index d6c7aa2..ad66e7b 100644 --- a/src/layouts/restaurant-page-layout.jsx +++ b/src/layouts/restaurant-page-layout.jsx @@ -1,4 +1,4 @@ -import { Outlet } from "react-router"; +import { Outlet, useParams } from "react-router"; import styles from "../components/restaurant-tabs/restaurant-tabs.module.css"; import { RestaurantTabContainer } from "../components/restaurant-tab-container/restaurant-tab-container.jsx"; import { Breadcrumbs } from "../components/breadcrumbs/breadcrumbs.jsx"; @@ -7,18 +7,20 @@ import { FULFILLED } from "../data/entities/request/sliсe.js"; import { requestText } from "../data/hooks/use-request.js"; export const RestaurantPageLayout = ({ - restaurantsIds, - restaurant, - requestStatus, - restaurantRequestStatus + restaurants, + requestStatus }) => { + const { restaurantId } = useParams(); + + const currentRestaurant = restaurants?.find((restaurant) => restaurant.id === restaurantId); + const breadcrumbs = [ { text: "Рестораны", path: RESTAURANT_PAGE }, { - text: restaurant?.name || "..." + text: currentRestaurant?.name || "..." } ]; @@ -31,14 +33,15 @@ export const RestaurantPageLayout = ({ ( <>
    - {restaurantsIds.map((id) => ( + {restaurants?.map((restaurant) => ( ))}
    - + )} diff --git a/src/pages/dish-page/dish-page-container.jsx b/src/pages/dish-page/dish-page-container.jsx index 542ef91..094371f 100644 --- a/src/pages/dish-page/dish-page-container.jsx +++ b/src/pages/dish-page/dish-page-container.jsx @@ -4,15 +4,14 @@ import { selectDishById } from "../../data/entities/dishes/slice.js"; import { DishPage } from "./dish-page.jsx"; import { useRequest } from "../../data/hooks/use-request.js"; import { getDish } from "../../data/entities/dishes/get-dish.js"; +import { useGetDishByIdQuery } from "../../data/services/api/api.js"; export const DishPageContainer = () => { const { dishId } = useParams(); - const requestStatus = useRequest(getDish, dishId); - const dish = useSelector((state) => selectDishById(state, dishId)); + // const requestStatus = useRequest(getDish, dishId); + // const dish = useSelector((state) => selectDishById(state, dishId)); - if (!dish) { - return null; - } + const { data: dish, status: requestStatus } = useGetDishByIdQuery(dishId); return { - const { restaurant, restaurantRequestStatus } = useOutletContext(); + + const { restaurantId } = useParams(); + + const { data: restaurant, status: restaurantRequestStatus } = useGetRestaurantByIdQuery(restaurantId); return ( ); diff --git a/src/pages/restaurant-home-page/restaurant-home-page.jsx b/src/pages/restaurant-home-page/restaurant-home-page.jsx index b05c8f0..1d0085b 100644 --- a/src/pages/restaurant-home-page/restaurant-home-page.jsx +++ b/src/pages/restaurant-home-page/restaurant-home-page.jsx @@ -1,17 +1,19 @@ import styles from "./restaurant-home-page.module.css"; import { Link } from "../../components/link/link.jsx"; -import { Outlet } from "react-router"; +import { Outlet, useOutletContext } from "react-router"; import buttonStyles from "../../components/button/button.module.css"; import { MENU_PAGE, REVIEWS_PAGE } from "../links-paths.js"; import { FULFILLED } from "../../data/entities/request/sliсe.js"; import { requestText } from "../../data/hooks/use-request.js"; export const RestaurantHomePage = ({ restaurant, restaurantRequestStatus }) => { - const { name, img } = restaurant; + + const {currentRestaurant} = useOutletContext(); + return (
    - name -

    {name}

    + name +

    {currentRestaurant?.name}

    {restaurantRequestStatus !== FULFILLED ? requestText(restaurantRequestStatus) : ( diff --git a/src/pages/restaurant-reviews-page/restaurant-reviews-page-container.jsx b/src/pages/restaurant-reviews-page/restaurant-reviews-page-container.jsx index 7ffc3ab..28a1f84 100644 --- a/src/pages/restaurant-reviews-page/restaurant-reviews-page-container.jsx +++ b/src/pages/restaurant-reviews-page/restaurant-reviews-page-container.jsx @@ -1,21 +1,36 @@ import { useLoginContext } from "../../components/login-context/hooks.js"; import { RestaurantReviewsPage } from "./restaurant-reviews-page.jsx"; import { useOutletContext } from "react-router"; -import { useRequest } from "../../data/hooks/use-request.js"; -import { getReviews } from "../../data/entities/reviews/get-reviews.js"; -import { getUsers } from "../../data/entities/users/get-users.js"; -import { FULFILLED, PENDING } from "../../data/entities/request/sliсe.js"; +import { useAddReviewMutation, useGetReviewsQuery } from "../../data/services/api/api.js"; export const RestaurantReviewsPageContainer = () => { const { restaurant } = useOutletContext(); const { user } = useLoginContext(); - const requestStatusUser = useRequest(getUsers); - const requestStatusReviews = useRequest(getReviews, restaurant.id); - const requestStatus = requestStatusUser === FULFILLED && requestStatusReviews === FULFILLED ? - FULFILLED : PENDING; + // const requestStatusUser = useRequest(getUsers); + // const requestStatusReviews = useRequest(getReviews, restaurant.id); + // const requestStatus = requestStatusUser === FULFILLED && requestStatusReviews === FULFILLED ? + // FULFILLED : PENDING; + + const { data: restaurantReviews, status: requestStatus } = useGetReviewsQuery(restaurant.id); + + + const [addReview, isAddReviewLoading] = useAddReviewMutation(); + + const handelOnReview = (form) => { + addReview({ + restaurantId: restaurant.id, + review: { + text: form.comment, + rating: form.rating, + userId: user.userId + } + }); + }; return ; + requestStatus={requestStatus} + onReviewSubmit={handelOnReview} + />; }; \ No newline at end of file diff --git a/src/pages/restaurant-reviews-page/restaurant-reviews-page.jsx b/src/pages/restaurant-reviews-page/restaurant-reviews-page.jsx index d2e20d6..1662cd2 100644 --- a/src/pages/restaurant-reviews-page/restaurant-reviews-page.jsx +++ b/src/pages/restaurant-reviews-page/restaurant-reviews-page.jsx @@ -3,12 +3,18 @@ import { ReviewForm } from "../../components/review-form/review-form.jsx"; import { FULFILLED } from "../../data/entities/request/sliсe.js"; import { requestText } from "../../data/hooks/use-request.js"; -export const RestaurantReviewsPage = ({ user, reviewsIds, restaurantId, requestStatus }) => { - return requestStatus !== FULFILLED ? +export const RestaurantReviewsPage = ({ + user, + reviews, + restaurantId, + requestStatus, + onReviewSubmit + }) => { + return requestStatus !== FULFILLED && !reviews ? requestText(requestStatus) : ( <> - - {user.name && } + + {user.name && } ); }; \ No newline at end of file diff --git a/src/pages/restaurants-page/restaurants-page-container.jsx b/src/pages/restaurants-page/restaurants-page-container.jsx index 39c3912..fa901fb 100644 --- a/src/pages/restaurants-page/restaurants-page-container.jsx +++ b/src/pages/restaurants-page/restaurants-page-container.jsx @@ -1,11 +1,11 @@ -import { useSelector } from "react-redux"; -import { selectRestaurantsIds } from "../../data/entities/restaurants/slice.js"; -import { getRestaurants } from "../../data/entities/restaurants/get-restaurants.js"; -import { useRequest } from "../../data/hooks/use-request.js"; import { RestaurantsPage } from "./restaurants-page.jsx"; +import { useGetRestaurantsQuery } from "../../data/services/api/api.js"; export const RestaurantsPageContainer = () => { - const requestStatus = useRequest(getRestaurants); - const restaurantsIds = useSelector(selectRestaurantsIds); - return ; + // const requestStatus = useRequest(getRestaurants); + // const restaurantsIds = useSelector(selectRestaurantsIds); + + const { data: restaurants, status: requestStatus } = useGetRestaurantsQuery(); + + return ; }; \ No newline at end of file diff --git a/src/pages/restaurants-page/restaurants-page.jsx b/src/pages/restaurants-page/restaurants-page.jsx index 329fdcd..01b049a 100644 --- a/src/pages/restaurants-page/restaurants-page.jsx +++ b/src/pages/restaurants-page/restaurants-page.jsx @@ -1,10 +1,10 @@ import styles from "../../components/restaurant-tabs/restaurant-tabs.module.css"; -import { RestaurantTileContainer } from "../../components/restaurant-tile/restaurant-tile-container.jsx"; import { Breadcrumbs } from "../../components/breadcrumbs/breadcrumbs.jsx"; import { requestText } from "../../data/hooks/use-request.js"; import { FULFILLED } from "../../data/entities/request/sliсe.js"; +import RestaurantTile from "../../components/restaurant-tile/restaurant-tile.jsx"; -export const RestaurantsPage = ({ requestStatus, restaurantsIds }) => { +export const RestaurantsPage = ({ requestStatus, restaurants }) => { const breadcrumbs = [ { text: "Рестораны" @@ -18,8 +18,13 @@ export const RestaurantsPage = ({ requestStatus, restaurantsIds }) => { {requestStatus !== FULFILLED ? requestText(requestStatus) : (
    - {restaurantsIds.map((id) => ( - + {restaurants.map((restaurant) => ( + ))}
    ) }