Skip to content

Commit

Permalink
Merge pull request #156 from kakao-tech-campus-2nd-step3/Feature/이미지API-
Browse files Browse the repository at this point in the history
#147

Feature/이미지api #147
  • Loading branch information
jasper200207 authored Nov 20, 2024
2 parents 4cf07bb + f09d5f8 commit 7930a97
Show file tree
Hide file tree
Showing 17 changed files with 191 additions and 82 deletions.
7 changes: 4 additions & 3 deletions src/api/farmApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ const useGetFarm = () => {
});
};

const useGetFarms = () => {
const useGetFarms = (categoryId: number | null = 0) => {
const fetcher = () => defaultApi.get(`/api/farms`).then(({ data }) => data);
const categoryFetcher = () => defaultApi.get(`/api/farms/category/${categoryId}`).then(({ data }) => data);

return useQuery({
queryKey: ["farms"],
queryFn: fetcher,
queryKey: ["farms", categoryId],
queryFn: categoryId !== 0 ? categoryFetcher : fetcher,
});
};

Expand Down
63 changes: 56 additions & 7 deletions src/api/imageApi.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,68 @@
import { useMutation } from "@tanstack/react-query";
import { needAuthDefaultApi } from "@api/axiosInstance";
import { externalApi, needAuthDefaultApi } from "@api/axiosInstance";

type ImageKeyPrefix = "FARM" | "FARM_REVIEW" | "PRODUCT" | "PRODUCT_REVIEW" | "MEMBER_PROFILE" | "PRODUCT_INTRO";

const getImageUpdatePresignedUrl = async (keyPrefix: ImageKeyPrefix) =>
needAuthDefaultApi.get("/api/s3/presigned-url-put", { params: { keyPrefix } });

const putImage2S3 = async (presignedUrl: string, image: File) => {
const formData = new FormData();
formData.append("image", image as Blob);

return externalApi.put(presignedUrl, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
};

const putImage = async (type: ImageKeyPrefix, image: File) => {
const getPresignedUrlResponse = await getImageUpdatePresignedUrl(type);
if (!getPresignedUrlResponse.data) {
throw new Error("Failed to get presigned URL");
}
const { presignedPutUrl, keyName, objectUrl } = getPresignedUrlResponse.data;
const putImageResponse = await putImage2S3(presignedPutUrl, image);
if (putImageResponse.status === 200) {
return {
imageUrl: objectUrl,
objectKey: keyName,
};
}
throw new Error("Failed to put image");
};

type PostImageProps = {
type: ImageKeyPrefix;
image: File;
referenceId: number;
};
const usePostImage = () => {
const fetcher = (img: string) => {
const formData = new FormData();
formData.append("image", img);
return needAuthDefaultApi.post("/api/images", formData).then(res => res.data);
};
const fetcher = async ({ type, image, referenceId }: PostImageProps) =>
needAuthDefaultApi.post("/api/images", {
...(await putImage(type, image)),
type,
referenceId,
});

return useMutation({
mutationFn: fetcher,
onSuccess: data => data,
});
};

const usePostImages = () => null;
const usePostImages = () => {
const fetcher = async (images: PostImageProps[]) => {
const promises = images.map(image => putImage(image.type, image.image));
const results = await Promise.all(promises);
return needAuthDefaultApi.post("/api/images", results);
};

return useMutation({
mutationFn: fetcher,
onSuccess: data => data,
});
};

export { usePostImage, usePostImages };
7 changes: 4 additions & 3 deletions src/api/productApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ type ProductData = {
subDesc3: string;
};

const useGetProducts = () => {
const useGetProducts = (categoryId = 0) => {
const fetcher = () => defaultApi.get(`/api/products`).then(({ data }) => data);
const categoryFetcher = () => defaultApi.get(`/api/categories/${categoryId}`).then(({ data }) => data);

return useQuery({
queryKey: ["products"],
queryFn: fetcher,
queryKey: ["products", categoryId],
queryFn: categoryId ? categoryFetcher : fetcher,
});
};

Expand Down
4 changes: 2 additions & 2 deletions src/components/features/MyPage/Order/CancelOrderModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import { Flex, ModalBody, ModalHeader, ModalCloseButton, Button, Text, Divider } from "@chakra-ui/react";
import { useGetProductDetail } from "@api/productApi";
import BasicModal from "@components/common/modal/BasicModal";
import useGetProduct from "./useGetProduct";

interface CancelOrderModalProps {
isOpen: boolean;
Expand All @@ -18,7 +18,7 @@ const CancelOrderModal: React.FC<CancelOrderModalProps> = ({
maxH = "800px",
productId,
}) => {
const { data: productData } = useGetProduct(productId);
const { data: productData } = useGetProductDetail(productId);

const [totalRefundAmount, setTotalRefundAmount] = useState(0);

Expand Down
4 changes: 2 additions & 2 deletions src/components/features/MyPage/Order/RefundModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import { Flex, ModalBody, ModalHeader, ModalCloseButton, Button, Text, Divider } from "@chakra-ui/react";
import { useGetProductDetail } from "@api/productApi";
import BasicModal from "@components/common/modal/BasicModal";
import useGetProduct from "./useGetProduct";

interface RefundModalProps {
isOpen: boolean;
Expand All @@ -12,7 +12,7 @@ interface RefundModalProps {
}

const RefundModal: React.FC<RefundModalProps> = ({ isOpen, onClose, maxW = "600px", maxH = "800px", productId }) => {
const { data: productData } = useGetProduct(productId);
const { data: productData } = useGetProductDetail(productId);

const [totalRefundAmount, setTotalRefundAmount] = useState(0);

Expand Down
14 changes: 0 additions & 14 deletions src/components/features/MyPage/Order/useGetProduct.tsx

This file was deleted.

5 changes: 4 additions & 1 deletion src/components/features/RegisterPage/Farmer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom";
import { Button, Text, Flex, Input, Image } from "@chakra-ui/react";
import { useCreateFarmer } from "@api/emailApi";
import call from "@assets/logo/Call.png";
import useLogin from "@hooks/useLogin";

type FarmerData = {
name: string;
Expand All @@ -26,10 +27,12 @@ const Farmer = () => {
const { mutateAsync: createFarmer } = useCreateFarmer();
const navigate = useNavigate();

const { refreshLogin } = useLogin();

const handleRegister = () => {
createFarmer(farmerData)
.then(() => {
navigate("/");
refreshLogin().then(() => navigate("/"));
})
.catch(() => {
alert("농부 등록에 실패했습니다.");
Expand Down
9 changes: 8 additions & 1 deletion src/components/features/SchedulePage/BestScheduleSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ const BestScheduleSection = () => (
<Text py="3" fontSize="2xl" fontWeight="bold">
이번주 Best
</Text>
<GridView items={mockBestSchedule} columns={2} gap="20px" ItemComponent={ScheduleCard} />
<GridView
items={mockBestSchedule.map(item => ({
...item,
}))}
columns={2}
gap="20px"
ItemComponent={ScheduleCard}
/>
</Box>
);

Expand Down
16 changes: 10 additions & 6 deletions src/components/features/SchedulePage/ScheduleCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import ImageCard, { ImageCardProps } from "@components/common/ImageCard";
import { Schedule } from "@type/index";

type ScheduleCardProps = ImageCardProps & {
item: Schedule;
item: Schedule & { link?: string };
};

const ScheduleCard = ({ item, ...props }: ScheduleCardProps) => (
<Box as={Link} to={`/schedule/${item.id}`}>
<Box {...(item.link ? { as: Link, to: item.link } : {})}>
<ImageCard
h="full"
{...props}
Expand All @@ -17,13 +17,17 @@ const ScheduleCard = ({ item, ...props }: ScheduleCardProps) => (
brightness: item?.mainImage ? 1 : 0.6,
}}
bgImg={item.mainImage}
_hover={{
transform: "scale(1.05)",
}}
_hover={
item.link
? {
transform: "scale(1.05)",
}
: {}
}
>
<Flex align="center" direction="column" w="full" mt="auto" mb="10">
<Text px="5" color="green" bg="white" borderRadius="xl">
{item.farm.address}
{item.address}
</Text>
<Text color="white" fontSize="2xl" fontWeight="bold">
{item.name}
Expand Down
10 changes: 5 additions & 5 deletions src/components/features/StorePage/ProductCard.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Link } from "react-router-dom";
import { Flex, Text } from "@chakra-ui/react";
import { Box, Flex, Text } from "@chakra-ui/react";
import Avatar from "@components/common/Avatar";
import Card, { CardProps } from "@components/common/Card";
import Image from "@components/common/Image";
import { Product } from "@type/index";

export type ProductCardProps = {
item: Product;
item: Product & { link?: string };
} & CardProps;

const ProductCard = ({ item, ...props }: ProductCardProps) => (
<Link to={`/store/${item.id}`}>
<Card {...props} _hover={{ transform: "translateY(-10px)" }}>
<Box {...(!item.link && { as: Link, to: item.link })}>
<Card {...props} _hover={item.link ? { transform: "translateY(-10px)" } : {}}>
<Image w="full" borderRadius="2xl" alt={item.name} aspectRatio="1" src={item.mainImage} />
<Flex py="3">
<Text maxW="70%" fontSize="lg" fontWeight="bold" isTruncated>
Expand All @@ -34,7 +34,7 @@ const ProductCard = ({ item, ...props }: ProductCardProps) => (
<Text ml="1">{item.farm.name}</Text>
</Flex>
</Card>
</Link>
</Box>
);

export default ProductCard;
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
import { Divider, Flex, Text } from "@chakra-ui/react";
import { useGetProductCategories } from "@api/categoryApi";
import ProductGroupView from "@components/features/StorePage/ProductList/ProductFilter/ProductGroupView";
import { ProductFilterItem } from "@components/features/StorePage/ProductList/ProductFilter/type";
import productGroup from "@constants/productGroup";
import useFilters, { UseFilters } from "@hooks/useFilters";
import { encodeCategory } from "@utils/categoryParser";

type ProductFilterProps = {
items?: ProductFilterItem[];
filterState?: UseFilters;
};

const ProductFilter = ({ items = [], filterState = useFilters() }: ProductFilterProps) => (
<Flex direction="column" w="250px" h="100%" pt="10">
<Text w="full" py="5" fontSize="xl" fontWeight="bold">
필터
</Text>
<Divider />
{productGroup.map(group => (
<ProductGroupView key={group.group} group={group} items={items} filterState={filterState} />
))}
</Flex>
);
const ProductFilter = ({ filterState = useFilters() }: ProductFilterProps) => {
const { data: productCategories } = useGetProductCategories();

return (
<Flex direction="column" w="250px" h="100%" pt="10">
<Text w="full" py="5" fontSize="xl" fontWeight="bold">
필터
</Text>
<Divider />
{productGroup.map(group => (
<ProductGroupView
key={group.group}
group={group}
items={(productCategories || []).map(encodeCategory)}
filterState={filterState}
/>
))}
</Flex>
);
};

export default ProductFilter;
12 changes: 9 additions & 3 deletions src/components/features/StorePage/ProductList/ProductsView.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import { useGetProducts } from "@api/productApi";
import GridView, { GridViewProps } from "@components/ItemView/GridView";
import ProductCard from "@components/features/StorePage/ProductCard";
import mockProducts from "@mocks/mockItem/mockProducts";
import { Product } from "@type/index";

type ProductsViewProps = {
filters: string[];
} & Omit<GridViewProps<Product>, "items" | "ItemComponent">;

const ProductsView = ({ filters, ...props }: ProductsViewProps) => {
const filteredProducts = mockProducts.filter(() => filters.length === 0);
const { data: products } = useGetProducts(filters[0] ? Number(filters[0]) : 0);

return <GridView items={filteredProducts} ItemComponent={ProductCard} {...props} />;
return (
<GridView
items={(products || []).map((p: Product) => ({ ...p, link: `/store/${p.id}` }))}
ItemComponent={ProductCard}
{...props}
/>
);
};

export default ProductsView;
4 changes: 1 addition & 3 deletions src/components/features/StorePage/ProductList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ import { Flex } from "@chakra-ui/react";
import ProductFilter from "@components/features/StorePage/ProductList/ProductFilter";
import ProductsView from "@components/features/StorePage/ProductList/ProductsView";
import useFilters from "@hooks/useFilters";
import mockCategory from "@mocks/mockItem/mockCategory";
import { encodeCategory } from "@utils/categoryParser";

const ProductList = () => {
const productFilterState = useFilters();

return (
<Flex gap="5" w="100%" h="100vh">
<ProductFilter filterState={productFilterState} items={mockCategory.map(encodeCategory)} />
<ProductFilter filterState={productFilterState} />
<Flex overflow="scroll" w="100%" h="100%">
<ProductsView filters={productFilterState.filters} py="10" columns={3} gap="25px" />
</Flex>
Expand Down
Loading

0 comments on commit 7930a97

Please sign in to comment.