diff --git a/src/api/axiosInstance.ts b/src/api/axiosInstance.ts index 254d0f5..f4bed3d 100644 --- a/src/api/axiosInstance.ts +++ b/src/api/axiosInstance.ts @@ -19,8 +19,8 @@ const needAuthDefaultApi = axios.create({ baseURL: process.env.REACT_APP_API_URL, headers: { "Content-Type": "application/json", - Authorization: JSON.parse(localStorage.getItem("poomasi_user") || "{}")?.token - ? `Bearer ${JSON.parse(localStorage.getItem("poomasi_user") || "{}").token}` + Authorization: JSON.parse(localStorage.getItem("poomasi-user") || "{}")?.token + ? `Bearer ${JSON.parse(localStorage.getItem("poomasi-user") || "{}").token}` : undefined, }, withCredentials: true, diff --git a/src/api/businessApi.ts b/src/api/businessApi.ts index 6213e86..c6b98ee 100644 --- a/src/api/businessApi.ts +++ b/src/api/businessApi.ts @@ -13,6 +13,6 @@ const useCreateBusiness = () => { return useMutation({ mutationFn: fetcher }); }; -const useGetBusinessDetail = () => null; +const useGetBusiness = () => null; -export { useCreateBusiness, useGetBusinessDetail }; +export { useCreateBusiness, useGetBusiness }; diff --git a/src/api/emailApi.ts b/src/api/emailApi.ts index b633852..71ba09f 100644 --- a/src/api/emailApi.ts +++ b/src/api/emailApi.ts @@ -13,11 +13,21 @@ type LoginData = { password: string; }; -type FarmerData = { +type FarmerRegisterData = { name: string; address: string; phone: string; }; + +type FarmerData = { + name: string; + email: string; + password: string; + phoneNumber: string; + storeName: string; + storeAddress: string; +}; + type AddressData = { defaultAddress: string; addressDetail: string; @@ -39,7 +49,7 @@ const useLoginEmail = () => { }; const useCreateFarmer = () => { - const fetcher = (farmerData: FarmerData) => + const fetcher = (farmerData: FarmerRegisterData) => needAuthDefaultApi.put(`/api/members/to-farmer`, farmerData).then(({ data }) => data); return useMutation({ mutationFn: fetcher }); }; @@ -58,6 +68,20 @@ const useUpdateEmail = () => { }); }; +const useUpdateFarmer = () => { + const queryClient = useQueryClient(); + const fetcher = (updateData: FarmerData) => + needAuthDefaultApi.put(`/api/members/update/farmer`, updateData).then(({ data }) => data); + + return useMutation({ + mutationFn: fetcher, + onSuccess: () => + queryClient.invalidateQueries({ + queryKey: ["farmer"], + }), + }); +}; + const useUpdateAddress = () => { const queryClient = useQueryClient(); const fetcher = (updateData: AddressData) => @@ -82,4 +106,12 @@ const useDeleteAccount = () => { }); }; -export { useCreateEmail, useLoginEmail, useUpdateEmail, useUpdateAddress, useCreateFarmer, useDeleteAccount }; +export { + useCreateEmail, + useLoginEmail, + useUpdateEmail, + useUpdateAddress, + useCreateFarmer, + useUpdateFarmer, + useDeleteAccount, +}; diff --git a/src/api/farmApi.ts b/src/api/farmApi.ts index 49691c3..266d025 100644 --- a/src/api/farmApi.ts +++ b/src/api/farmApi.ts @@ -18,6 +18,22 @@ type FarmData = { endTime: string; }; +type ReservationData = { + farmId: number; + startTime: string; + endTime: string; + date: ["startDate", "endDate"]; +}; + +const useGetFarm = () => { + const fetcher = () => defaultApi.get(`/api/farms/{farmId}/detail`).then(({ data }) => data); + + return useQuery({ + queryKey: ["farms"], + queryFn: fetcher, + }); +}; + const useGetFarms = () => { const fetcher = () => defaultApi.get(`/api/farms`).then(({ data }) => data); @@ -37,7 +53,7 @@ const useGetFarmDetail = (farmId: number) => { }; const useCreateFarms = () => { - const fetcher = (farmData: FarmData) => defaultApi.post(`/api/farms`, farmData).then(({ data }) => data); + const fetcher = (farmData: FarmData) => defaultApi.post(`/api/farmer/farms`, farmData).then(({ data }) => data); return useMutation({ mutationFn: fetcher }); }; @@ -57,4 +73,42 @@ const useUpdateFarms = (farmId: number) => { }); }; -export { useGetFarms, useGetFarmDetail, useCreateFarms, useUpdateFarms }; +const useDeleteFarms = (farmId: number) => { + const queryClient = useQueryClient(); + + const fetcher = () => defaultApi.delete(`/api/farmer/farms/${farmId}`).then(({ data }) => data); + + return useMutation({ + mutationFn: fetcher, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["farms"] }); + }, + }); +}; + +const useCreateReservations = () => { + const fetcher = (reservationData: ReservationData) => + defaultApi.post(`/api/farms/schedule`, reservationData).then(({ data }) => data); + + return useMutation({ mutationFn: fetcher }); +}; + +const useGetReservations = (farmId: number) => { + const fetcher = () => defaultApi.get(`/api/farms/schedule?farmId=17&year=2024&month=10`).then(({ data }) => data); + + return useQuery({ + queryKey: ["reservations", farmId], + queryFn: fetcher, + }); +}; + +export { + useGetFarm, + useGetFarms, + useGetFarmDetail, + useCreateFarms, + useUpdateFarms, + useDeleteFarms, + useCreateReservations, + useGetReservations, +}; diff --git a/src/api/intercepters.ts b/src/api/intercepters.ts index 22e29df..e2032ea 100644 --- a/src/api/intercepters.ts +++ b/src/api/intercepters.ts @@ -13,10 +13,10 @@ const refrechIntercepter = async (response: AxiosResponse): Promise { diff --git a/src/api/reviewApi.ts b/src/api/reviewApi.ts new file mode 100644 index 0000000..3cc1c70 --- /dev/null +++ b/src/api/reviewApi.ts @@ -0,0 +1,80 @@ +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { defaultApi } from "@api/axiosInstance"; + +type ReviewData = { + rating: number; + content: string; + product_id?: number; + farm_id?: number; +}; + +const useGetProductReviews = (reviewId: number) => { + const fetcher = () => defaultApi.get(`/api/products/{product_id}/reviews`).then(({ data }) => data); + + return useQuery({ + queryKey: ["reviews", reviewId], + queryFn: fetcher, + }); +}; + +const useCreateProductReviews = () => { + const fetcher = (reviewData: ReviewData) => + defaultApi.post(`/api/products/{product_id}/reviews`, reviewData).then(({ data }) => data); + + return useMutation({ mutationFn: fetcher }); +}; + +const useUpdateReviews = (reviewId: number) => { + const queryClient = useQueryClient(); + + const fetcher = (reviewData: ReviewData) => + defaultApi.put(`/api/reviews/{review_id}`, reviewData).then(({ data }) => data); + + return useMutation({ + mutationFn: fetcher, + onSuccess: () => + queryClient.invalidateQueries({ + queryKey: ["reviews", reviewId], + }), + }); +}; + +const useDeleteReviews = (reviewId: number) => { + const queryClient = useQueryClient(); + + const fetcher = () => defaultApi.delete(`/api/reviews/${reviewId}`).then(({ data }) => data); + + return useMutation({ + mutationFn: fetcher, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["reviews", reviewId], + }); + }, + }); +}; + +const useGetFarmReviews = (reviewId: number) => { + const fetcher = () => defaultApi.get(`/api/farms/{farm_id}/reviews`).then(({ data }) => data); + + return useQuery({ + queryKey: ["reviews", reviewId], + queryFn: fetcher, + }); +}; + +const useCreateFarmReviews = () => { + const fetcher = (reviewData: ReviewData) => + defaultApi.post(`/api/farms/{reservationId}/reviews`, reviewData).then(({ data }) => data); + + return useMutation({ mutationFn: fetcher }); +}; + +export { + useGetProductReviews, + useCreateProductReviews, + useUpdateReviews, + useDeleteReviews, + useGetFarmReviews, + useCreateFarmReviews, +}; diff --git a/src/api/storeApi.ts b/src/api/storeApi.ts new file mode 100644 index 0000000..c54041a --- /dev/null +++ b/src/api/storeApi.ts @@ -0,0 +1,30 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { defaultApi } from "@api/axiosInstance"; + +type StoreData = { + name: string; + address: string; + phone: number; +}; + +const useCreateStore = () => { + const fetcher = (storeData: StoreData) => defaultApi.post(`/api/stores`, storeData).then(({ data }) => data); + + return useMutation({ mutationFn: fetcher }); +}; + +const useUpdateStore = () => { + const queryClient = useQueryClient(); + + const fetcher = (storeData: StoreData) => defaultApi.put(`/api/stores`, storeData).then(({ data }) => data); + + return useMutation({ + mutationFn: fetcher, + onSuccess: () => + queryClient.invalidateQueries({ + queryKey: ["store"], + }), + }); +}; + +export { useCreateStore, useUpdateStore }; diff --git a/src/api/wishlistApi.ts b/src/api/wishlistApi.ts new file mode 100644 index 0000000..1a777e2 --- /dev/null +++ b/src/api/wishlistApi.ts @@ -0,0 +1,30 @@ +import { useQuery, useMutation } from "@tanstack/react-query"; +import { defaultApi, needAuthDefaultApi } from "@api/axiosInstance"; + +type WishlistData = { + product_id: number; +}; + +const useCreateWishlists = () => { + const fetcher = (wishlistData: WishlistData) => + defaultApi.post(`/api/v1/wishlist/{product_id}`, wishlistData).then(({ data }) => data); + + return useMutation({ mutationFn: fetcher }); +}; + +const useGetWishlists = (type: "product" | "farm") => { + const fetcher = () => needAuthDefaultApi.get(`/api/v1/wishlist`, { params: { type } }).then(({ data }) => data); + + return useQuery({ + queryKey: ["wishlists"], + queryFn: fetcher, + }); +}; + +const useDeleteWishlists = () => { + const fetcher = (wishlistId: number) => defaultApi.delete(`/api/v1/wishlist/${wishlistId}`).then(({ data }) => data); + + return useMutation({ mutationFn: fetcher }); +}; + +export { useCreateWishlists, useGetWishlists, useDeleteWishlists }; diff --git a/src/atoms/userAtom.ts b/src/atoms/userAtom.ts index fac45de..8b0cbfc 100644 --- a/src/atoms/userAtom.ts +++ b/src/atoms/userAtom.ts @@ -7,7 +7,7 @@ type UserAtomValue = { role: string; }; -const userAtom = atom(localStorage.getItem("poomasi_user") ?? ""); +const userAtom = atom(localStorage.getItem("poomasi-user") ?? ""); const userAtomWithPersistence = atom( get => { @@ -16,7 +16,7 @@ const userAtomWithPersistence = atom( }, (_, set, data: UserAtomValue | null) => { set(userAtom, JSON.stringify(data)); - localStorage.setItem("poomasi_user", JSON.stringify(data)); + localStorage.setItem("poomasi-user", JSON.stringify(data)); }, ); diff --git a/src/components/common/KaKaoLoginButton.tsx b/src/components/common/KaKaoLoginButton.tsx index 489d350..0034fbe 100644 --- a/src/components/common/KaKaoLoginButton.tsx +++ b/src/components/common/KaKaoLoginButton.tsx @@ -4,7 +4,7 @@ import Image from "@components/common/Image"; type KaKaoLoginButtonProps = ImageProps; const KaKaoLoginButton = ({ ...props }: KaKaoLoginButtonProps) => { - const link = "https://api.poomasi.shop/oauth2/authentication/kakao"; + const link = `${process.env.REACT_APP_API_URL}/oauth2/authentication/kakao`; return ( diff --git a/src/components/features/FarmDetailPage/ProductDescription.tsx b/src/components/features/FarmDetailPage/ProductDescription.tsx deleted file mode 100644 index 3d99cf7..0000000 --- a/src/components/features/FarmDetailPage/ProductDescription.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { Box, Text, Flex } from "@chakra-ui/react"; -import { useGetFarmDetail } from "@api/farmApi"; -import Image from "@components/common/Image"; - -const defaultInfo = { - title: "", - mainImage: "", - detailTitles: ["", "", ""], - detailDescriptions: ["", "", ""], - detailImages: ["", "", ""], -}; - -const ProductDescription = ({ scheduleId }: { scheduleId: number }) => { - const { data: farmDetail = defaultInfo } = useGetFarmDetail(scheduleId); - - return ( - - - {farmDetail.title} - - - mainImage - - - - - - {farmDetail.detailTitles[0]} - - - {farmDetail.detailDescriptions[0]} - - - detailImage - - - - - - - {farmDetail.detailTitles[1]} - - - {farmDetail.detailDescriptions[1]} - - - detailImage - - - - - - - {farmDetail.detailTitles[2]} - - - {farmDetail.detailDescriptions[2]} - - - detailImage - - - - - - ); -}; - -export default ProductDescription; diff --git a/src/components/features/FarmDetailPage/Product.tsx b/src/components/features/FarmDetailPage/Schedule.tsx similarity index 88% rename from src/components/features/FarmDetailPage/Product.tsx rename to src/components/features/FarmDetailPage/Schedule.tsx index e422c52..7fd53f9 100644 --- a/src/components/features/FarmDetailPage/Product.tsx +++ b/src/components/features/FarmDetailPage/Schedule.tsx @@ -1,7 +1,5 @@ import { useState, useEffect } from "react"; - -import { HeartOutlined } from "@ant-design/icons"; -import { Box, Button, Text, Flex, Icon, Divider, Select } from "@chakra-ui/react"; +import { Box, Button, Text, Flex, Divider, Select } from "@chakra-ui/react"; import { useGetFarmDetail } from "@api/farmApi"; import Image from "@components/common/Image"; @@ -19,7 +17,7 @@ const defaultFormData = { endTime: "", }; -const Product = ({ scheduleId }: { scheduleId: number }) => { +const Schedule = ({ scheduleId }: { scheduleId: number }) => { const { data: farmDetail = defaultFormData } = useGetFarmDetail(scheduleId); const [dateOptions, setDateOptions] = useState([]); @@ -40,7 +38,7 @@ const Product = ({ scheduleId }: { scheduleId: number }) => { return ( - + farmImage @@ -53,20 +51,6 @@ const Product = ({ scheduleId }: { scheduleId: number }) => { {farmDetail.price}원 / {farmDetail.maxTeam}팀(최대인원: {farmDetail.maxPeople}명) - - - { ); }; -export default Product; +export default Schedule; diff --git a/src/components/features/IntroductionPage/Introduction.tsx b/src/components/features/IntroductionPage/Introduction.tsx index eb49bb7..5a5f858 100644 --- a/src/components/features/IntroductionPage/Introduction.tsx +++ b/src/components/features/IntroductionPage/Introduction.tsx @@ -8,17 +8,18 @@ import Intro7 from "@assets/Image/Intro/Intro7.png"; import Intro8 from "@assets/Image/Intro/Intro8.png"; import Image from "@components/common/Image"; import ImageCard from "@components/common/ImageCard"; +import size from "@constants/size"; const Introduction = () => ( - + - + 우리가 꿈꾸는 내일, 농민과 고객이 함께 만드는 새로운 유통 - + 품앗이 이야기 @@ -87,7 +88,7 @@ const Introduction = () => ( Intro6 - + 농업인의 땀과 소비자의 마음을 직접 이어,
@@ -132,7 +133,7 @@ const Introduction = () => (
- + ); export default Introduction; diff --git a/src/components/features/MyPage/Category.tsx b/src/components/features/MyPage/Category.tsx index d4607e2..36227da 100644 --- a/src/components/features/MyPage/Category.tsx +++ b/src/components/features/MyPage/Category.tsx @@ -3,7 +3,7 @@ import { HeartOutlined, ProfileOutlined, UserOutlined } from "@ant-design/icons" import { Text, Flex, Box, Icon, Link as ChakraLink } from "@chakra-ui/react"; const Category = () => ( - + @@ -11,11 +11,18 @@ const Category = () => ( 위시리스트 - - 농산품 - - - 농장 체험 + + 위시리스트 diff --git a/src/components/features/MyPage/Order/ContactModal.tsx b/src/components/features/MyPage/Order/ContactModal.tsx index 391868c..088fd32 100644 --- a/src/components/features/MyPage/Order/ContactModal.tsx +++ b/src/components/features/MyPage/Order/ContactModal.tsx @@ -6,7 +6,7 @@ import CancelOrderModal from "./CancelOrderModal"; import ContactNumberModal from "./ContactNumberModal"; interface ContactModalProps { - status: "CANCANCLE" | "CANREFUND" | "OTHER"; + status: "CANCANCEL" | "CANREFUND" | "OTHER"; isOpen: boolean; onClose: () => void; maxW?: string; @@ -101,7 +101,7 @@ const ContactModal: React.FC = ({ {status === "CANREFUND" && ( )} - {status === "CANCANCLE" && ( + {status === "CANCANCEL" && ( )} diff --git a/src/components/features/MyPage/Order/FarmList.tsx b/src/components/features/MyPage/Order/FarmList.tsx index 25a161d..77f4fd5 100644 --- a/src/components/features/MyPage/Order/FarmList.tsx +++ b/src/components/features/MyPage/Order/FarmList.tsx @@ -1,56 +1,92 @@ +import { useState } from "react"; import { Flex, Button, Text } from "@chakra-ui/react"; import farm1 from "@assets/Image/Farm/Farm1.png"; import Image from "@components/common/Image"; +import { Farm } from "@type/index"; +import ContactNumberModal from "./ContactNumberModal"; +import ReviewModal from "./ReviewModal"; -const FarmList = () => ( - +const FarmList = ({ item }: { item: Farm }) => { + const [isReviewModalOpen, setIsReviewModalOpen] = useState(false); + const [isContactModalOpen, setIsContactModalOpen] = useState(false); + + const handleOpenReviewModal = () => { + setIsReviewModalOpen(true); + }; + + const handleCloseReviewModal = () => { + setIsReviewModalOpen(false); + }; + + const handleOpenContactModal = () => { + setIsContactModalOpen(true); + }; + + const handleCloseContactModal = () => { + setIsContactModalOpen(false); + }; + + return ( - - 2024.09.14 - - 지민이네 복숭아 농장 + {item.name} - - Farm image - - 가끔 지민은 학교에서 심각하게 집에 가고싶을 때가 있어요. 그럴 때면 로만이나 자스민에서 산 달달한 복숭아 - 아이스티를 마시며 향수병을 달랩니다. - - - + + Farm image + + + + 주소 :  + + + {item.address} + + - - - + + 한 줄 소개 + + + {item.description} + + + + + {isContactModalOpen && } + + {isReviewModalOpen && } + + - -); + ); +}; export default FarmList; diff --git a/src/components/features/MyPage/Order/ReviewList.tsx b/src/components/features/MyPage/Order/ReviewList.tsx index 14e5d3a..30a1088 100644 --- a/src/components/features/MyPage/Order/ReviewList.tsx +++ b/src/components/features/MyPage/Order/ReviewList.tsx @@ -1,54 +1,94 @@ -import { Flex, Button, Text } from "@chakra-ui/react"; -import store8 from "@assets/Image/Store/Store8.png"; +import { useState } from "react"; +import { Flex, Button, Text, Alert } from "@chakra-ui/react"; + +import { useDeleteReviews } from "@api/reviewApi"; import Image from "@components/common/Image"; +import { Review } from "@type/index"; +import ReviewModal from "./ReviewModal"; + +const ReviewList = ({ item }: { item: Review }) => { + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const { mutate: deleteReview } = useDeleteReviews(item.id); + + const handleDelete = () => { + deleteReview(undefined, { + onSuccess: () => { + ; + }, + onError: () => { + ; + }, + }); + }; + + const handleEditClick = () => { + setIsEditModalOpen(true); + }; + + const handleEditModalClose = () => { + setIsEditModalOpen(false); + }; + + return ( + <> + + + + + {item.name} + + Review image + + {item.description} + + -const ReviewList = () => ( - - - - 2024.09.14 - - - - 건호네 하나뿐인 미나리 - - Review image - - 기대 안하고 샀는데 생각보다 괜찮아요. - - - - - - - - -); + + + + + + + + + ); +}; export default ReviewList; diff --git a/src/components/features/MyPage/Order/ReviewModal.tsx b/src/components/features/MyPage/Order/ReviewModal.tsx index 6f2f578..ebf0695 100644 --- a/src/components/features/MyPage/Order/ReviewModal.tsx +++ b/src/components/features/MyPage/Order/ReviewModal.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from "react"; +import React, { useRef, useState, useEffect } from "react"; import { UploadOutlined } from "@ant-design/icons"; import { Flex, @@ -10,8 +10,10 @@ import { Divider, Icon, Input, - Box, + Textarea, + Alert, } from "@chakra-ui/react"; +import { useCreateFarmReviews, useCreateProductReviews, useUpdateReviews } from "@api/reviewApi"; import Image from "@components/common/Image"; import StarRating from "@components/common/StarRating"; import BasicModal from "@components/common/modal/BasicModal"; @@ -20,14 +22,39 @@ type ReviewModalProps = { isOpen: boolean; onClose: () => void; productId?: number; + farmId?: number; + isEditing?: boolean; + initialData?: { + rating: number; + content: string; + id: number; + }; }; -const ReviewModal: React.FC = ({ isOpen, onClose, productId }) => { +const ReviewModal: React.FC = ({ + isOpen, + onClose, + productId, + farmId, + isEditing = false, + initialData, +}) => { const mainImageInputRef = useRef(null); const [imageUrl, setImageUrl] = useState(); const [rating, setRating] = useState(0); const [reviewText, setReviewText] = useState(""); + const { mutate: createProductReview } = useCreateProductReviews(); + const { mutate: createFarmReview } = useCreateFarmReviews(); + const { mutate: updateReview } = useUpdateReviews(initialData?.id || 0); + + useEffect(() => { + if (isEditing && initialData) { + setRating(initialData.rating); + setReviewText(initialData.content); + } + }, [isEditing, initialData]); + const handleImageChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { @@ -39,22 +66,67 @@ const ReviewModal: React.FC = ({ isOpen, onClose, productId }) setRating(newRating); }; - const handleReviewTextChange = (event: React.ChangeEvent) => { + const handleReviewTextChange = (event: React.ChangeEvent) => { setReviewText(event.target.value); }; const handleReviewSubmit = () => { const reviewData = { - productId, rating, - reviewText, - imageUrl, + content: reviewText, }; - postMessage(reviewData); + if (isEditing && initialData) { + updateReview( + { ...reviewData, farm_id: initialData.id }, + { + onSuccess: () => { + ; + setTimeout(onClose, 2000); + }, + onError: () => { + ; + }, + }, + ); + } else { + if (productId) { + createProductReview( + { ...reviewData, product_id: productId }, + { + onSuccess: () => { + ; + setTimeout(onClose, 2000); + }, + onError: () => { + ; + }, + }, + ); + } + + if (farmId) { + createFarmReview( + { ...reviewData, farm_id: farmId }, + { + onSuccess: () => { + ; + setTimeout(onClose, 2000); + }, + onError: () => { + ; + }, + }, + ); + } + } + }; + + const handleCancel = () => { + onClose(); }; return ( - + 후기 쓰기 @@ -80,32 +152,34 @@ const ReviewModal: React.FC = ({ isOpen, onClose, productId }) as={UploadOutlined} mx="5" mt={12} - ml={-200} + ml={-230} color="#000000" fontSize="30px" cursor="pointer" onClick={() => mainImageInputRef.current?.click()} /> - + main image - - - - +